mirror of
https://github.com/immich-app/immich.git
synced 2026-03-22 16:39:26 +03:00
Videos have recently been changed to support zooming, but this can make the controls in the centre of the screen unergonomic as they will either stay in the centre when dismissing, or stick to the video when zooming. Neither is great. We should align the behaviour with other apps which has the play/pause toggle at the bottom of the screen with the seeker bar instead. Co-authored-by: Alex <alex.tran1502@gmail.com>
145 lines
4.3 KiB
Dart
145 lines
4.3 KiB
Dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
|
|
class AssetViewerState {
|
|
final double backgroundOpacity;
|
|
final bool showingDetails;
|
|
final bool showingControls;
|
|
final bool isZoomed;
|
|
final BaseAsset? currentAsset;
|
|
final int stackIndex;
|
|
|
|
const AssetViewerState({
|
|
this.backgroundOpacity = 1.0,
|
|
this.showingDetails = false,
|
|
this.showingControls = true,
|
|
this.isZoomed = false,
|
|
this.currentAsset,
|
|
this.stackIndex = 0,
|
|
});
|
|
|
|
AssetViewerState copyWith({
|
|
double? backgroundOpacity,
|
|
bool? showingDetails,
|
|
bool? showingControls,
|
|
bool? isZoomed,
|
|
BaseAsset? currentAsset,
|
|
int? stackIndex,
|
|
}) {
|
|
return AssetViewerState(
|
|
backgroundOpacity: backgroundOpacity ?? this.backgroundOpacity,
|
|
showingDetails: showingDetails ?? this.showingDetails,
|
|
showingControls: showingControls ?? this.showingControls,
|
|
isZoomed: isZoomed ?? this.isZoomed,
|
|
currentAsset: currentAsset ?? this.currentAsset,
|
|
stackIndex: stackIndex ?? this.stackIndex,
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return 'AssetViewerState(opacity: $backgroundOpacity, showingDetails: $showingDetails, controls: $showingControls, isZoomed: $isZoomed)';
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
if (other.runtimeType != runtimeType) return false;
|
|
return other is AssetViewerState &&
|
|
other.backgroundOpacity == backgroundOpacity &&
|
|
other.showingDetails == showingDetails &&
|
|
other.showingControls == showingControls &&
|
|
other.isZoomed == isZoomed &&
|
|
other.currentAsset == currentAsset &&
|
|
other.stackIndex == stackIndex;
|
|
}
|
|
|
|
@override
|
|
int get hashCode =>
|
|
backgroundOpacity.hashCode ^
|
|
showingDetails.hashCode ^
|
|
showingControls.hashCode ^
|
|
isZoomed.hashCode ^
|
|
currentAsset.hashCode ^
|
|
stackIndex.hashCode;
|
|
}
|
|
|
|
class AssetViewerStateNotifier extends Notifier<AssetViewerState> {
|
|
@override
|
|
AssetViewerState build() {
|
|
ref.listen(_watchedCurrentAssetProvider, (_, next) {
|
|
final updated = next.valueOrNull;
|
|
if (updated != null) {
|
|
state = state.copyWith(currentAsset: updated);
|
|
}
|
|
});
|
|
return const AssetViewerState();
|
|
}
|
|
|
|
void reset() {
|
|
state = const AssetViewerState();
|
|
}
|
|
|
|
void setAsset(BaseAsset asset) {
|
|
if (asset == state.currentAsset) return;
|
|
state = state.copyWith(currentAsset: asset, stackIndex: 0);
|
|
}
|
|
|
|
void setOpacity(double opacity) {
|
|
if (opacity == state.backgroundOpacity) {
|
|
return;
|
|
}
|
|
state = state.copyWith(backgroundOpacity: opacity, showingControls: opacity >= 1.0 ? true : state.showingControls);
|
|
}
|
|
|
|
void setShowingDetails(bool showing) {
|
|
if (showing == state.showingDetails) {
|
|
return;
|
|
}
|
|
state = state.copyWith(showingDetails: showing, showingControls: showing ? true : state.showingControls);
|
|
|
|
final heroTag = state.currentAsset?.heroTag;
|
|
if (heroTag != null) {
|
|
final notifier = ref.read(videoPlayerProvider(heroTag).notifier);
|
|
showing ? notifier.hold() : notifier.release();
|
|
}
|
|
}
|
|
|
|
void setControls(bool isShowing) {
|
|
if (isShowing == state.showingControls) {
|
|
return;
|
|
}
|
|
state = state.copyWith(showingControls: isShowing);
|
|
}
|
|
|
|
void toggleControls() {
|
|
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;
|
|
}
|
|
state = state.copyWith(stackIndex: index);
|
|
}
|
|
}
|
|
|
|
final assetViewerProvider = NotifierProvider<AssetViewerStateNotifier, AssetViewerState>(AssetViewerStateNotifier.new);
|
|
|
|
final _watchedCurrentAssetProvider = StreamProvider<BaseAsset?>((ref) {
|
|
ref.watch(assetViewerProvider.select((s) => s.currentAsset?.heroTag));
|
|
final asset = ref.read(assetViewerProvider).currentAsset;
|
|
if (asset == null) return const Stream.empty();
|
|
return ref.read(assetServiceProvider).watchAsset(asset);
|
|
});
|