Skip to content

Commit

Permalink
feat(mobile): stop asset grid rebuilds (immich-app#3226)
Browse files Browse the repository at this point in the history
* feat(mobile): stop asset grid rebuilds

* undo unnecessary changes

---------

Co-authored-by: Fynn Petersen-Frey <[email protected]>
  • Loading branch information
fyfrey and Fynn Petersen-Frey authored Jul 13, 2023
1 parent 863e983 commit f9739c9
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 113 deletions.
6 changes: 4 additions & 2 deletions mobile/lib/modules/asset_viewer/views/gallery_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -589,7 +591,7 @@ class GalleryViewerPage extends HookConsumerWidget {
},
imageProvider: provider,
heroAttributes: PhotoViewHeroAttributes(
tag: asset.id,
tag: asset.id + heroOffset,
),
filterQuality: FilterQuality.high,
tightMode: true,
Expand All @@ -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,
Expand Down
114 changes: 46 additions & 68 deletions mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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(),
),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -50,6 +51,7 @@ class ImmichAssetGridView extends StatefulWidget {
this.showMultiSelectIndicator = true,
this.visibleItemsListener,
this.topWidget,
this.heroOffset = 0,
});

@override
Expand Down Expand Up @@ -122,6 +124,7 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
: null,
useGrayBoxPlaceholder: true,
showStorageIndicator: widget.showStorageIndicator,
heroOffset: widget.heroOffset,
);
}

Expand Down
30 changes: 4 additions & 26 deletions mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ThumbnailImage extends HookConsumerWidget {
final bool multiselectEnabled;
final Function? onSelect;
final Function? onDeselect;
final int heroOffset;

const ThumbnailImage({
Key? key,
Expand All @@ -31,6 +32,7 @@ class ThumbnailImage extends HookConsumerWidget {
this.multiselectEnabled = false,
this.onDeselect,
this.onSelect,
this.heroOffset = 0,
}) : super(key: key);

@override
Expand Down Expand Up @@ -63,6 +65,7 @@ class ThumbnailImage extends HookConsumerWidget {
initialIndex: index,
loadAsset: loadAsset,
totalAssets: totalAssets,
heroOffset: heroOffset,
),
);
}
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/modules/memories/ui/memory_lane.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class MemoryLane extends HookConsumerWidget {
onTap: () {
HapticFeedback.heavyImpact();
AutoRouter.of(context).push(
VerticalRouteView(
MemoryRoute(
memories: memories,
memoryIndex: index,
),
Expand Down
38 changes: 22 additions & 16 deletions mobile/lib/routing/router.gr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class _$AppRouter extends RootStackRouter {
initialIndex: args.initialIndex,
loadAsset: args.loadAsset,
totalAssets: args.totalAssets,
heroOffset: args.heroOffset,
),
);
},
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -506,7 +507,7 @@ class _$AppRouter extends RootStackRouter {
),
RouteConfig(
AlbumViewerRoute.name,
path: '/album-viewer-page',
path: '/',
guards: [
authGuard,
duplicateGuard,
Expand Down Expand Up @@ -601,8 +602,8 @@ class _$AppRouter extends RootStackRouter {
],
),
RouteConfig(
VerticalRouteView.name,
path: '/vertical-page-view',
MemoryRoute.name,
path: '/memory-page',
guards: [
authGuard,
duplicateGuard,
Expand Down Expand Up @@ -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',
Expand All @@ -688,6 +690,7 @@ class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
initialIndex: initialIndex,
loadAsset: loadAsset,
totalAssets: totalAssets,
heroOffset: heroOffset,
),
);

Expand All @@ -700,6 +703,7 @@ class GalleryViewerRouteArgs {
required this.initialIndex,
required this.loadAsset,
required this.totalAssets,
this.heroOffset = 0,
});

final Key? key;
Expand All @@ -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}';
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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}';
}
}

Expand Down

0 comments on commit f9739c9

Please sign in to comment.