mirror of
https://github.com/immich-app/immich.git
synced 2025-10-18 12:30:37 -04:00
fix(mobile): ensure current asset is set in asset viewer (#21504)
This commit is contained in:
parent
f06b054087
commit
873f7921da
@ -176,13 +176,13 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
|||||||
final isColdStart = currentRouteName == null || currentRouteName == SplashScreenRoute.name;
|
final isColdStart = currentRouteName == null || currentRouteName == SplashScreenRoute.name;
|
||||||
|
|
||||||
if (deepLink.uri.scheme == "immich") {
|
if (deepLink.uri.scheme == "immich") {
|
||||||
final proposedRoute = await deepLinkHandler.handleScheme(deepLink, isColdStart);
|
final proposedRoute = await deepLinkHandler.handleScheme(deepLink, ref, isColdStart);
|
||||||
|
|
||||||
return proposedRoute;
|
return proposedRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deepLink.uri.host == "my.immich.app") {
|
if (deepLink.uri.host == "my.immich.app") {
|
||||||
final proposedRoute = await deepLinkHandler.handleMyImmichApp(deepLink, isColdStart);
|
final proposedRoute = await deepLinkHandler.handleMyImmichApp(deepLink, ref, isColdStart);
|
||||||
|
|
||||||
return proposedRoute;
|
return proposedRoute;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,18 @@ class AssetViewer extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState createState() => _AssetViewerState();
|
ConsumerState createState() => _AssetViewerState();
|
||||||
|
|
||||||
|
static void setAsset(WidgetRef ref, BaseAsset asset) {
|
||||||
|
// Always holds the current asset from the timeline
|
||||||
|
ref.read(assetViewerProvider.notifier).setAsset(asset);
|
||||||
|
// The currentAssetNotifier actually holds the current asset that is displayed
|
||||||
|
// which could be stack children as well
|
||||||
|
ref.read(currentAssetNotifier.notifier).setAsset(asset);
|
||||||
|
if (asset.isVideo || asset.isMotionPhoto) {
|
||||||
|
ref.read(videoPlaybackValueProvider.notifier).reset();
|
||||||
|
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const double _kBottomSheetMinimumExtent = 0.4;
|
const double _kBottomSheetMinimumExtent = 0.4;
|
||||||
@ -99,13 +111,12 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
assert(ref.read(currentAssetNotifier) != null, "Current asset should not be null when opening the AssetViewer");
|
||||||
pageController = PageController(initialPage: widget.initialIndex);
|
pageController = PageController(initialPage: widget.initialIndex);
|
||||||
platform = widget.platform ?? const LocalPlatform();
|
platform = widget.platform ?? const LocalPlatform();
|
||||||
totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
||||||
bottomSheetController = DraggableScrollableController();
|
bottomSheetController = DraggableScrollableController();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback(_onAssetInit);
|
||||||
_onAssetChanged(widget.initialIndex);
|
|
||||||
});
|
|
||||||
reloadSubscription = EventStream.shared.listen(_onEvent);
|
reloadSubscription = EventStream.shared.listen(_onEvent);
|
||||||
heroOffset = widget.heroOffset ?? TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
|
heroOffset = widget.heroOffset ?? TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
|
||||||
}
|
}
|
||||||
@ -143,26 +154,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
return provider.resolve(ImageConfiguration.empty)..addListener(_dummyListener);
|
return provider.resolve(ImageConfiguration.empty)..addListener(_dummyListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAssetChanged(int index) async {
|
void _precacheAssets(int index) {
|
||||||
// Validate index bounds and try to get asset, loading buffer if needed
|
|
||||||
final timelineService = ref.read(timelineServiceProvider);
|
final timelineService = ref.read(timelineServiceProvider);
|
||||||
final asset = await timelineService.getAssetAsync(index);
|
unawaited(timelineService.preCacheAssets(index));
|
||||||
|
|
||||||
if (asset == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always holds the current asset from the timeline
|
|
||||||
ref.read(assetViewerProvider.notifier).setAsset(asset);
|
|
||||||
// The currentAssetNotifier actually holds the current asset that is displayed
|
|
||||||
// which could be stack children as well
|
|
||||||
ref.read(currentAssetNotifier.notifier).setAsset(asset);
|
|
||||||
if (asset.isVideo || asset.isMotionPhoto) {
|
|
||||||
ref.read(videoPlaybackValueProvider.notifier).reset();
|
|
||||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
unawaited(ref.read(timelineServiceProvider).preCacheAssets(index));
|
|
||||||
_cancelTimers();
|
_cancelTimers();
|
||||||
// This will trigger the pre-caching of adjacent assets ensuring
|
// This will trigger the pre-caching of adjacent assets ensuring
|
||||||
// that they are ready when the user navigates to them.
|
// that they are ready when the user navigates to them.
|
||||||
@ -181,12 +175,29 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
_nextPreCacheStream = nextAsset != null ? _precacheImage(nextAsset) : null;
|
_nextPreCacheStream = nextAsset != null ? _precacheImage(nextAsset) : null;
|
||||||
});
|
});
|
||||||
_delayedOperations.add(timer);
|
_delayedOperations.add(timer);
|
||||||
|
|
||||||
_handleCasting(asset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleCasting(BaseAsset asset) {
|
void _onAssetInit(Duration _) {
|
||||||
|
_precacheAssets(widget.initialIndex);
|
||||||
|
_handleCasting();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onAssetChanged(int index) async {
|
||||||
|
final timelineService = ref.read(timelineServiceProvider);
|
||||||
|
final asset = await timelineService.getAssetAsync(index);
|
||||||
|
if (asset == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetViewer.setAsset(ref, asset);
|
||||||
|
_precacheAssets(index);
|
||||||
|
_handleCasting();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleCasting() {
|
||||||
if (!ref.read(castProvider).isCasting) return;
|
if (!ref.read(castProvider).isCasting) return;
|
||||||
|
final asset = ref.read(currentAssetNotifier);
|
||||||
|
if (asset == null) return;
|
||||||
|
|
||||||
// hide any casting snackbars if they exist
|
// hide any casting snackbars if they exist
|
||||||
context.scaffoldMessenger.hideCurrentSnackBar();
|
context.scaffoldMessenger.hideCurrentSnackBar();
|
||||||
@ -597,7 +608,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
if (asset == null) return;
|
if (asset == null) return;
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_handleCasting(asset);
|
_handleCasting();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,14 +75,23 @@ class AssetViewerStateNotifier extends AutoDisposeNotifier<AssetViewerState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setAsset(BaseAsset? asset) {
|
void setAsset(BaseAsset? asset) {
|
||||||
|
if (asset == state.currentAsset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
state = state.copyWith(currentAsset: asset, stackIndex: 0);
|
state = state.copyWith(currentAsset: asset, stackIndex: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOpacity(int opacity) {
|
void setOpacity(int opacity) {
|
||||||
|
if (opacity == state.backgroundOpacity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
state = state.copyWith(backgroundOpacity: opacity, showingControls: opacity == 255 ? true : state.showingControls);
|
state = state.copyWith(backgroundOpacity: opacity, showingControls: opacity == 255 ? true : state.showingControls);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBottomSheet(bool showing) {
|
void setBottomSheet(bool showing) {
|
||||||
|
if (showing == state.showingBottomSheet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
state = state.copyWith(showingBottomSheet: showing, showingControls: showing ? true : state.showingControls);
|
state = state.copyWith(showingBottomSheet: showing, showingControls: showing ? true : state.showingControls);
|
||||||
if (showing) {
|
if (showing) {
|
||||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||||
@ -90,6 +99,9 @@ class AssetViewerStateNotifier extends AutoDisposeNotifier<AssetViewerState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setControls(bool isShowing) {
|
void setControls(bool isShowing) {
|
||||||
|
if (isShowing == state.showingControls) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
state = state.copyWith(showingControls: isShowing);
|
state = state.copyWith(showingControls: isShowing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +110,9 @@ class AssetViewerStateNotifier extends AutoDisposeNotifier<AssetViewerState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setStackIndex(int index) {
|
void setStackIndex(int index) {
|
||||||
|
if (index == state.stackIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
state = state.copyWith(stackIndex: index);
|
state = state.copyWith(stackIndex: index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail_tile.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail_tile.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/fixed/row.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/fixed/row.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/header.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/header.widget.dart';
|
||||||
@ -155,6 +156,7 @@ class _AssetTileWidget extends ConsumerWidget {
|
|||||||
} else {
|
} else {
|
||||||
await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1);
|
await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1);
|
||||||
ref.read(isPlayingMotionVideoProvider.notifier).playing = false;
|
ref.read(isPlayingMotionVideoProvider.notifier).playing = false;
|
||||||
|
AssetViewer.setAsset(ref, asset);
|
||||||
ctx.pushRoute(
|
ctx.pushRoute(
|
||||||
AssetViewerRoute(
|
AssetViewerRoute(
|
||||||
initialIndex: assetIndex,
|
initialIndex: assetIndex,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/services/asset.service.dart' as beta_asset_service;
|
import 'package:immich_mobile/domain/services/asset.service.dart' as beta_asset_service;
|
||||||
import 'package:immich_mobile/domain/services/memory.service.dart';
|
import 'package:immich_mobile/domain/services/memory.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/remote_album.service.dart';
|
import 'package:immich_mobile/domain/services/remote_album.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
|
||||||
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider;
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider;
|
||||||
@ -16,7 +18,6 @@ import 'package:immich_mobile/services/album.service.dart';
|
|||||||
import 'package:immich_mobile/services/asset.service.dart';
|
import 'package:immich_mobile/services/asset.service.dart';
|
||||||
import 'package:immich_mobile/services/memory.service.dart';
|
import 'package:immich_mobile/services/memory.service.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
|
|
||||||
final deepLinkServiceProvider = Provider(
|
final deepLinkServiceProvider = Provider(
|
||||||
(ref) => DeepLinkService(
|
(ref) => DeepLinkService(
|
||||||
@ -71,14 +72,14 @@ class DeepLinkService {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DeepLink> handleScheme(PlatformDeepLink link, bool isColdStart) async {
|
Future<DeepLink> handleScheme(PlatformDeepLink link, WidgetRef ref, bool isColdStart) async {
|
||||||
// get everything after the scheme, since Uri cannot parse path
|
// get everything after the scheme, since Uri cannot parse path
|
||||||
final intent = link.uri.host;
|
final intent = link.uri.host;
|
||||||
final queryParams = link.uri.queryParameters;
|
final queryParams = link.uri.queryParameters;
|
||||||
|
|
||||||
PageRouteInfo<dynamic>? deepLinkRoute = switch (intent) {
|
PageRouteInfo<dynamic>? deepLinkRoute = switch (intent) {
|
||||||
"memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''),
|
"memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''),
|
||||||
"asset" => await _buildAssetDeepLink(queryParams['id'] ?? ''),
|
"asset" => await _buildAssetDeepLink(queryParams['id'] ?? '', ref),
|
||||||
"album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''),
|
"album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
@ -95,7 +96,7 @@ class DeepLinkService {
|
|||||||
return _handleColdStart(deepLinkRoute, isColdStart);
|
return _handleColdStart(deepLinkRoute, isColdStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DeepLink> handleMyImmichApp(PlatformDeepLink link, bool isColdStart) async {
|
Future<DeepLink> handleMyImmichApp(PlatformDeepLink link, WidgetRef ref, bool isColdStart) async {
|
||||||
final path = link.uri.path;
|
final path = link.uri.path;
|
||||||
|
|
||||||
const uuidRegex = r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';
|
const uuidRegex = r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';
|
||||||
@ -105,7 +106,7 @@ class DeepLinkService {
|
|||||||
PageRouteInfo<dynamic>? deepLinkRoute;
|
PageRouteInfo<dynamic>? deepLinkRoute;
|
||||||
if (assetRegex.hasMatch(path)) {
|
if (assetRegex.hasMatch(path)) {
|
||||||
final assetId = assetRegex.firstMatch(path)?.group(1) ?? '';
|
final assetId = assetRegex.firstMatch(path)?.group(1) ?? '';
|
||||||
deepLinkRoute = await _buildAssetDeepLink(assetId);
|
deepLinkRoute = await _buildAssetDeepLink(assetId, ref);
|
||||||
} else if (albumRegex.hasMatch(path)) {
|
} else if (albumRegex.hasMatch(path)) {
|
||||||
final albumId = albumRegex.firstMatch(path)?.group(1) ?? '';
|
final albumId = albumRegex.firstMatch(path)?.group(1) ?? '';
|
||||||
deepLinkRoute = await _buildAlbumDeepLink(albumId);
|
deepLinkRoute = await _buildAlbumDeepLink(albumId);
|
||||||
@ -141,13 +142,14 @@ class DeepLinkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PageRouteInfo?> _buildAssetDeepLink(String assetId) async {
|
Future<PageRouteInfo?> _buildAssetDeepLink(String assetId, WidgetRef ref) async {
|
||||||
if (Store.isBetaTimelineEnabled) {
|
if (Store.isBetaTimelineEnabled) {
|
||||||
final asset = await _betaAssetService.getRemoteAsset(assetId);
|
final asset = await _betaAssetService.getRemoteAsset(assetId);
|
||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssetViewer.setAsset(ref, asset);
|
||||||
return AssetViewerRoute(initialIndex: 0, timelineService: _betaTimelineFactory.fromAssets([asset]));
|
return AssetViewerRoute(initialIndex: 0, timelineService: _betaTimelineFactory.fromAssets([asset]));
|
||||||
} else {
|
} else {
|
||||||
// TODO: Remove this when beta is default
|
// TODO: Remove this when beta is default
|
||||||
|
Loading…
x
Reference in New Issue
Block a user