feat(mobile): inline asset details (#25952)

The existing implementation for showing asset details uses a bottom
sheet, and is not in sync with the preview or scroll intent. Other apps
use inline details, which is much cleaner and feels better to use.
This commit is contained in:
Thomas
2026-02-17 15:24:34 +00:00
committed by GitHub
parent 06d487782e
commit 5c6433b4ca
42 changed files with 1518 additions and 1277 deletions
@@ -3,31 +3,35 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provi
import 'package:riverpod_annotation/riverpod_annotation.dart';
class AssetViewerState {
final int backgroundOpacity;
final bool showingBottomSheet;
final double backgroundOpacity;
final bool showingDetails;
final bool showingControls;
final bool isZoomed;
final BaseAsset? currentAsset;
final int stackIndex;
const AssetViewerState({
this.backgroundOpacity = 255,
this.showingBottomSheet = false,
this.backgroundOpacity = 1.0,
this.showingDetails = false,
this.showingControls = true,
this.isZoomed = false,
this.currentAsset,
this.stackIndex = 0,
});
AssetViewerState copyWith({
int? backgroundOpacity,
bool? showingBottomSheet,
double? backgroundOpacity,
bool? showingDetails,
bool? showingControls,
bool? isZoomed,
BaseAsset? currentAsset,
int? stackIndex,
}) {
return AssetViewerState(
backgroundOpacity: backgroundOpacity ?? this.backgroundOpacity,
showingBottomSheet: showingBottomSheet ?? this.showingBottomSheet,
showingDetails: showingDetails ?? this.showingDetails,
showingControls: showingControls ?? this.showingControls,
isZoomed: isZoomed ?? this.isZoomed,
currentAsset: currentAsset ?? this.currentAsset,
stackIndex: stackIndex ?? this.stackIndex,
);
@@ -35,7 +39,7 @@ class AssetViewerState {
@override
String toString() {
return 'AssetViewerState(opacity: $backgroundOpacity, bottomSheet: $showingBottomSheet, controls: $showingControls)';
return 'AssetViewerState(opacity: $backgroundOpacity, showingDetails: $showingDetails, controls: $showingControls, isZoomed: $isZoomed)';
}
@override
@@ -44,8 +48,9 @@ class AssetViewerState {
if (other.runtimeType != runtimeType) return false;
return other is AssetViewerState &&
other.backgroundOpacity == backgroundOpacity &&
other.showingBottomSheet == showingBottomSheet &&
other.showingDetails == showingDetails &&
other.showingControls == showingControls &&
other.isZoomed == isZoomed &&
other.currentAsset == currentAsset &&
other.stackIndex == stackIndex;
}
@@ -53,8 +58,9 @@ class AssetViewerState {
@override
int get hashCode =>
backgroundOpacity.hashCode ^
showingBottomSheet.hashCode ^
showingDetails.hashCode ^
showingControls.hashCode ^
isZoomed.hashCode ^
currentAsset.hashCode ^
stackIndex.hashCode;
}
@@ -76,18 +82,18 @@ class AssetViewerStateNotifier extends Notifier<AssetViewerState> {
state = state.copyWith(currentAsset: asset, stackIndex: 0);
}
void setOpacity(int opacity) {
void setOpacity(double opacity) {
if (opacity == state.backgroundOpacity) {
return;
}
state = state.copyWith(backgroundOpacity: opacity, showingControls: opacity == 255 ? true : state.showingControls);
state = state.copyWith(backgroundOpacity: opacity, showingControls: opacity >= 1.0 ? true : state.showingControls);
}
void setBottomSheet(bool showing) {
if (showing == state.showingBottomSheet) {
void setShowingDetails(bool showing) {
if (showing == state.showingDetails) {
return;
}
state = state.copyWith(showingBottomSheet: showing, showingControls: showing ? true : state.showingControls);
state = state.copyWith(showingDetails: showing, showingControls: showing ? true : state.showingControls);
if (showing) {
ref.read(videoPlayerControlsProvider.notifier).pause();
}
@@ -104,6 +110,13 @@ class AssetViewerStateNotifier extends Notifier<AssetViewerState> {
state = state.copyWith(showingControls: !state.showingControls);
}
void setZoomed(bool isZoomed) {
if (isZoomed == state.isZoomed) {
return;
}
state = state.copyWith(isZoomed: isZoomed);
}
void setStackIndex(int index) {
if (index == state.stackIndex) {
return;