Browse Source

feat(mobile): stop asset grid rebuilds (#3226)

* feat(mobile): stop asset grid rebuilds

* undo unnecessary changes

---------

Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
Fynn Petersen-Frey 2 years ago
parent
commit
f9739c9730

+ 4 - 2
mobile/lib/modules/asset_viewer/views/gallery_viewer.dart

@@ -37,12 +37,14 @@ class GalleryViewerPage extends HookConsumerWidget {
   final Asset Function(int index) loadAsset;
   final int totalAssets;
   final int initialIndex;
+  final int heroOffset;
 
   GalleryViewerPage({
     super.key,
     required this.initialIndex,
     required this.loadAsset,
     required this.totalAssets,
+    this.heroOffset = 0,
   }) : controller = PageController(initialPage: initialIndex);
 
   final PageController controller;
@@ -589,7 +591,7 @@ class GalleryViewerPage extends HookConsumerWidget {
                     },
                     imageProvider: provider,
                     heroAttributes: PhotoViewHeroAttributes(
-                      tag: asset.id,
+                      tag: asset.id + heroOffset,
                     ),
                     filterQuality: FilterQuality.high,
                     tightMode: true,
@@ -606,7 +608,7 @@ class GalleryViewerPage extends HookConsumerWidget {
                     onDragUpdate: (_, details, __) =>
                         handleSwipeUpDown(details),
                     heroAttributes: PhotoViewHeroAttributes(
-                      tag: asset.id,
+                      tag: asset.id + heroOffset,
                     ),
                     filterQuality: FilterQuality.high,
                     maxScale: 1.0,

+ 46 - 68
mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart

@@ -1,5 +1,6 @@
 import 'dart:math';
 
+import 'package:auto_route/auto_route.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
@@ -52,84 +53,61 @@ class ImmichAssetGrid extends HookConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     var settings = ref.watch(appSettingsServiceProvider);
 
-    // Needs to suppress hero animations when navigating to this widget
-    final enableHeroAnimations = useState(false);
-    final transitionDuration = ModalRoute.of(context)?.transitionDuration;
-
     final perRow = useState(
       assetsPerRow ?? settings.getSetting(AppSettingsEnum.tilesPerRow)!,
     );
     final scaleFactor = useState(7.0 - perRow.value);
     final baseScaleFactor = useState(7.0 - perRow.value);
 
-    useEffect(
-      () {
-        // Wait for transition to complete, then re-enable
-        if (transitionDuration == null) {
-          // No route transition found, maybe we opened this up first
-          enableHeroAnimations.value = true;
-        } else {
-          // Unfortunately, using the transition animation itself didn't
-          // seem to work reliably. So instead, wait until the duration of the
-          // animation has elapsed to re-enable the hero animations
-          Future.delayed(transitionDuration).then((_) {
-            enableHeroAnimations.value = true;
-          });
-        }
-        return null;
-      },
-      [],
-    );
-
-    Future<bool> onWillPop() async {
-      enableHeroAnimations.value = false;
-      return true;
+    /// assets need different hero tags across tabs / modals
+    /// otherwise, hero animations are performed across tabs (looks buggy!)
+    int heroOffset() {
+      const int range = 1152921504606846976; // 2^60
+      final tabScope = TabsRouterScope.of(context);
+      if (tabScope != null) {
+        final int tabIndex = tabScope.controller.activeIndex;
+        return tabIndex * range;
+      }
+      return range * 7;
     }
 
     Widget buildAssetGridView(RenderList renderList) {
-      return WillPopScope(
-        onWillPop: onWillPop,
-        child: HeroMode(
-          enabled: enableHeroAnimations.value,
-          child: RawGestureDetector(
-            gestures: {
-              CustomScaleGestureRecognizer:
-                  GestureRecognizerFactoryWithHandlers<
-                          CustomScaleGestureRecognizer>(
-                      () => CustomScaleGestureRecognizer(),
-                      (CustomScaleGestureRecognizer scale) {
-                scale.onStart = (details) {
-                  baseScaleFactor.value = scaleFactor.value;
-                };
+      return RawGestureDetector(
+        gestures: {
+          CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
+                  CustomScaleGestureRecognizer>(
+              () => CustomScaleGestureRecognizer(),
+              (CustomScaleGestureRecognizer scale) {
+            scale.onStart = (details) {
+              baseScaleFactor.value = scaleFactor.value;
+            };
 
-                scale.onUpdate = (details) {
-                  scaleFactor.value =
-                      max(min(5.0, baseScaleFactor.value * details.scale), 1.0);
-                  if (7 - scaleFactor.value.toInt() != perRow.value) {
-                    perRow.value = 7 - scaleFactor.value.toInt();
-                  }
-                };
-                scale.onEnd = (details) {};
-              })
-            },
-            child: ImmichAssetGridView(
-              onRefresh: onRefresh,
-              assetsPerRow: perRow.value,
-              listener: listener,
-              showStorageIndicator: showStorageIndicator ??
-                  settings.getSetting(AppSettingsEnum.storageIndicator),
-              renderList: renderList,
-              margin: margin,
-              selectionActive: selectionActive,
-              preselectedAssets: preselectedAssets,
-              canDeselect: canDeselect,
-              dynamicLayout: dynamicLayout ??
-                  settings.getSetting(AppSettingsEnum.dynamicLayout),
-              showMultiSelectIndicator: showMultiSelectIndicator,
-              visibleItemsListener: visibleItemsListener,
-              topWidget: topWidget,
-            ),
-          ),
+            scale.onUpdate = (details) {
+              scaleFactor.value =
+                  max(min(5.0, baseScaleFactor.value * details.scale), 1.0);
+              if (7 - scaleFactor.value.toInt() != perRow.value) {
+                perRow.value = 7 - scaleFactor.value.toInt();
+              }
+            };
+          })
+        },
+        child: ImmichAssetGridView(
+          onRefresh: onRefresh,
+          assetsPerRow: perRow.value,
+          listener: listener,
+          showStorageIndicator: showStorageIndicator ??
+              settings.getSetting(AppSettingsEnum.storageIndicator),
+          renderList: renderList,
+          margin: margin,
+          selectionActive: selectionActive,
+          preselectedAssets: preselectedAssets,
+          canDeselect: canDeselect,
+          dynamicLayout: dynamicLayout ??
+              settings.getSetting(AppSettingsEnum.dynamicLayout),
+          showMultiSelectIndicator: showMultiSelectIndicator,
+          visibleItemsListener: visibleItemsListener,
+          topWidget: topWidget,
+          heroOffset: heroOffset(),
         ),
       );
     }

+ 3 - 0
mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart

@@ -34,6 +34,7 @@ class ImmichAssetGridView extends StatefulWidget {
   final void Function(ItemPosition start, ItemPosition end)?
       visibleItemsListener;
   final Widget? topWidget;
+  final int heroOffset;
 
   const ImmichAssetGridView({
     super.key,
@@ -50,6 +51,7 @@ class ImmichAssetGridView extends StatefulWidget {
     this.showMultiSelectIndicator = true,
     this.visibleItemsListener,
     this.topWidget,
+    this.heroOffset = 0,
   });
 
   @override
@@ -122,6 +124,7 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
           : null,
       useGrayBoxPlaceholder: true,
       showStorageIndicator: widget.showStorageIndicator,
+      heroOffset: widget.heroOffset,
     );
   }
 

+ 4 - 26
mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart

@@ -18,6 +18,7 @@ class ThumbnailImage extends HookConsumerWidget {
   final bool multiselectEnabled;
   final Function? onSelect;
   final Function? onDeselect;
+  final int heroOffset;
 
   const ThumbnailImage({
     Key? key,
@@ -31,6 +32,7 @@ class ThumbnailImage extends HookConsumerWidget {
     this.multiselectEnabled = false,
     this.onDeselect,
     this.onSelect,
+    this.heroOffset = 0,
   }) : super(key: key);
 
   @override
@@ -63,6 +65,7 @@ class ThumbnailImage extends HookConsumerWidget {
               initialIndex: index,
               loadAsset: loadAsset,
               totalAssets: totalAssets,
+              heroOffset: heroOffset,
             ),
           );
         }
@@ -72,32 +75,7 @@ class ThumbnailImage extends HookConsumerWidget {
         HapticFeedback.heavyImpact();
       },
       child: Hero(
-        createRectTween: (begin, end) {
-          double? top;
-          // Uses the [BoxFit.contain] algorithm
-          if (asset.width != null && asset.height != null) {
-            final assetAR = asset.width! / asset.height!;
-            final w = MediaQuery.of(context).size.width;
-            final deviceAR = MediaQuery.of(context).size.aspectRatio;
-            if (deviceAR < assetAR) {
-              top = asset.height! * w / asset.width!;
-            } else {
-              top = 0;
-            }
-            // get the height offset
-          }
-
-          return MaterialRectCenterArcTween(
-            begin: Rect.fromLTRB(
-              0,
-              top ?? 0.0,
-              MediaQuery.of(context).size.width,
-              MediaQuery.of(context).size.height,
-            ),
-            end: end,
-          );
-        },
-        tag: asset.id,
+        tag: asset.id + heroOffset,
         child: Stack(
           children: [
             Container(

+ 1 - 1
mobile/lib/modules/memories/ui/memory_lane.dart

@@ -31,7 +31,7 @@ class MemoryLane extends HookConsumerWidget {
                           onTap: () {
                             HapticFeedback.heavyImpact();
                             AutoRouter.of(context).push(
-                              VerticalRouteView(
+                              MemoryRoute(
                                 memories: memories,
                                 memoryIndex: index,
                               ),

+ 22 - 16
mobile/lib/routing/router.gr.dart

@@ -70,6 +70,7 @@ class _$AppRouter extends RootStackRouter {
           initialIndex: args.initialIndex,
           loadAsset: args.loadAsset,
           totalAssets: args.totalAssets,
+          heroOffset: args.heroOffset,
         ),
       );
     },
@@ -290,8 +291,8 @@ class _$AppRouter extends RootStackRouter {
         child: const AllPeoplePage(),
       );
     },
-    VerticalRouteView.name: (routeData) {
-      final args = routeData.argsAs<VerticalRouteViewArgs>();
+    MemoryRoute.name: (routeData) {
+      final args = routeData.argsAs<MemoryRouteArgs>();
       return MaterialPageX<dynamic>(
         routeData: routeData,
         child: MemoryPage(
@@ -506,7 +507,7 @@ class _$AppRouter extends RootStackRouter {
         ),
         RouteConfig(
           AlbumViewerRoute.name,
-          path: '/album-viewer-page',
+          path: '/',
           guards: [
             authGuard,
             duplicateGuard,
@@ -601,8 +602,8 @@ class _$AppRouter extends RootStackRouter {
           ],
         ),
         RouteConfig(
-          VerticalRouteView.name,
-          path: '/vertical-page-view',
+          MemoryRoute.name,
+          path: '/memory-page',
           guards: [
             authGuard,
             duplicateGuard,
@@ -680,6 +681,7 @@ class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
     required int initialIndex,
     required Asset Function(int) loadAsset,
     required int totalAssets,
+    int heroOffset = 0,
   }) : super(
           GalleryViewerRoute.name,
           path: '/gallery-viewer-page',
@@ -688,6 +690,7 @@ class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
             initialIndex: initialIndex,
             loadAsset: loadAsset,
             totalAssets: totalAssets,
+            heroOffset: heroOffset,
           ),
         );
 
@@ -700,6 +703,7 @@ class GalleryViewerRouteArgs {
     required this.initialIndex,
     required this.loadAsset,
     required this.totalAssets,
+    this.heroOffset = 0,
   });
 
   final Key? key;
@@ -710,9 +714,11 @@ class GalleryViewerRouteArgs {
 
   final int totalAssets;
 
+  final int heroOffset;
+
   @override
   String toString() {
-    return 'GalleryViewerRouteArgs{key: $key, initialIndex: $initialIndex, loadAsset: $loadAsset, totalAssets: $totalAssets}';
+    return 'GalleryViewerRouteArgs{key: $key, initialIndex: $initialIndex, loadAsset: $loadAsset, totalAssets: $totalAssets, heroOffset: $heroOffset}';
   }
 }
 
@@ -1014,7 +1020,7 @@ class AlbumViewerRoute extends PageRouteInfo<AlbumViewerRouteArgs> {
     required int albumId,
   }) : super(
           AlbumViewerRoute.name,
-          path: '/album-viewer-page',
+          path: '/',
           args: AlbumViewerRouteArgs(
             key: key,
             albumId: albumId,
@@ -1302,26 +1308,26 @@ class AllPeopleRoute extends PageRouteInfo<void> {
 
 /// generated route for
 /// [MemoryPage]
-class VerticalRouteView extends PageRouteInfo<VerticalRouteViewArgs> {
-  VerticalRouteView({
+class MemoryRoute extends PageRouteInfo<MemoryRouteArgs> {
+  MemoryRoute({
     required List<Memory> memories,
     required int memoryIndex,
     Key? key,
   }) : super(
-          VerticalRouteView.name,
-          path: '/vertical-page-view',
-          args: VerticalRouteViewArgs(
+          MemoryRoute.name,
+          path: '/memory-page',
+          args: MemoryRouteArgs(
             memories: memories,
             memoryIndex: memoryIndex,
             key: key,
           ),
         );
 
-  static const String name = 'VerticalRouteView';
+  static const String name = 'MemoryRoute';
 }
 
-class VerticalRouteViewArgs {
-  const VerticalRouteViewArgs({
+class MemoryRouteArgs {
+  const MemoryRouteArgs({
     required this.memories,
     required this.memoryIndex,
     this.key,
@@ -1335,7 +1341,7 @@ class VerticalRouteViewArgs {
 
   @override
   String toString() {
-    return 'VerticalRouteViewArgs{memories: $memories, memoryIndex: $memoryIndex, key: $key}';
+    return 'MemoryRouteArgs{memories: $memories, memoryIndex: $memoryIndex, key: $key}';
   }
 }