chore: hero animations (#19860)

* remove herocontrollerscope

* handle heroOffset in new timeline

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2025-07-11 01:55:18 +05:30 committed by GitHub
parent 70b73145f1
commit 7d8f56b483
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 20 additions and 20 deletions

View File

@ -23,12 +23,12 @@ import 'package:immich_mobile/providers/theme.provider.dart';
import 'package:immich_mobile/routing/app_navigation_observer.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/services/deep_link.service.dart';
import 'package:immich_mobile/services/local_notification.service.dart';
import 'package:immich_mobile/theme/dynamic_theme.dart';
import 'package:immich_mobile/theme/theme_data.dart';
import 'package:immich_mobile/utils/bootstrap.dart';
import 'package:immich_mobile/utils/cache/widgets_binding.dart';
import 'package:immich_mobile/services/deep_link.service.dart';
import 'package:immich_mobile/utils/download.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:immich_mobile/utils/migration.dart';
@ -253,7 +253,8 @@ class ImmichAppState extends ConsumerState<ImmichApp>
),
routerConfig: router.config(
deepLinkBuilder: _deepLinkBuilder,
navigatorObservers: () => [AppNavigationObserver(ref: ref)],
navigatorObservers: () =>
[AppNavigationObserver(ref: ref), HeroController()],
),
),
);

View File

@ -4,13 +4,13 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/providers/multiselect.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@RoutePage()
class TabControllerPage extends HookConsumerWidget {
@ -158,10 +158,6 @@ class TabControllerPage extends HookConsumerWidget {
),
builder: (context, child) {
final tabsRouter = AutoTabsRouter.of(context);
final heroedChild = HeroControllerScope(
controller: HeroController(),
child: child,
);
return PopScope(
canPop: tabsRouter.activeIndex == 0,
onPopInvokedWithResult: (didPop, _) =>
@ -173,10 +169,10 @@ class TabControllerPage extends HookConsumerWidget {
children: [
navigationRail(tabsRouter),
const VerticalDivider(),
Expanded(child: heroedChild),
Expanded(child: child),
],
)
: heroedChild,
: child,
bottomNavigationBar: multiselectEnabled || isScreenLandscape
? null
: bottomNavigationBar(tabsRouter),

View File

@ -127,10 +127,6 @@ class TabShellPage extends ConsumerWidget {
),
builder: (context, child) {
final tabsRouter = AutoTabsRouter.of(context);
final heroedChild = HeroControllerScope(
controller: HeroController(),
child: child,
);
return PopScope(
canPop: tabsRouter.activeIndex == 0,
onPopInvokedWithResult: (didPop, _) =>
@ -142,10 +138,10 @@ class TabShellPage extends ConsumerWidget {
children: [
navigationRail(tabsRouter),
const VerticalDivider(),
Expanded(child: heroedChild),
Expanded(child: child),
],
)
: heroedChild,
: child,
bottomNavigationBar: _BottomNavigationBar(
tabsRouter: tabsRouter,
destinations: navigationDestinations,

View File

@ -71,6 +71,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
StreamSubscription? reloadSubscription;
late Platform platform;
late final int heroOffset;
late PhotoViewControllerValue initialPhotoViewState;
bool? hasDraggedDown;
bool isSnapping = false;
@ -98,6 +99,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
_onAssetChanged(widget.initialIndex);
});
reloadSubscription = EventStream.shared.listen(_onEvent);
heroOffset = TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
}
@override
@ -487,7 +489,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
return PhotoViewGalleryPageOptions(
key: ValueKey(asset.heroTag),
imageProvider: getFullImageProvider(asset, size: size),
heroAttributes: PhotoViewHeroAttributes(tag: asset.heroTag),
heroAttributes:
PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
filterQuality: FilterQuality.high,
tightMode: true,
initialScale: PhotoViewComputedScale.contained * 0.999,
@ -521,7 +524,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
onDragUpdate: _onDragUpdate,
onDragEnd: _onDragEnd,
onTapDown: _onTapDown,
heroAttributes: PhotoViewHeroAttributes(tag: asset.heroTag),
heroAttributes:
PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
filterQuality: FilterQuality.high,
initialScale: PhotoViewComputedScale.contained * 0.99,
maxScale: 1.0,

View File

@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
@ -25,6 +26,8 @@ class ThumbnailTile extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final heroOffset = TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
final assetContainerColor = context.isDarkTheme
? context.primaryColor.darken(amount: 0.4)
: context.primaryColor.lighten(amount: 0.75);
@ -64,7 +67,7 @@ class ThumbnailTile extends ConsumerWidget {
children: [
Positioned.fill(
child: Hero(
tag: asset.heroTag,
tag: '${asset.heroTag}_$heroOffset',
child: Thumbnail(
asset: asset,
fit: fit,