1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

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>
This commit is contained in:
Fynn Petersen-Frey 2023-07-13 17:42:06 +02:00 committed by GitHub
parent 863e983726
commit f9739c9730
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 113 deletions

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ class ThumbnailImage extends HookConsumerWidget {
final bool multiselectEnabled; final bool multiselectEnabled;
final Function? onSelect; final Function? onSelect;
final Function? onDeselect; final Function? onDeselect;
final int heroOffset;
const ThumbnailImage({ const ThumbnailImage({
Key? key, Key? key,
@ -31,6 +32,7 @@ class ThumbnailImage extends HookConsumerWidget {
this.multiselectEnabled = false, this.multiselectEnabled = false,
this.onDeselect, this.onDeselect,
this.onSelect, this.onSelect,
this.heroOffset = 0,
}) : super(key: key); }) : super(key: key);
@override @override
@ -63,6 +65,7 @@ class ThumbnailImage extends HookConsumerWidget {
initialIndex: index, initialIndex: index,
loadAsset: loadAsset, loadAsset: loadAsset,
totalAssets: totalAssets, totalAssets: totalAssets,
heroOffset: heroOffset,
), ),
); );
} }
@ -72,32 +75,7 @@ class ThumbnailImage extends HookConsumerWidget {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
}, },
child: Hero( child: Hero(
createRectTween: (begin, end) { tag: asset.id + heroOffset,
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,
child: Stack( child: Stack(
children: [ children: [
Container( Container(

View File

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

View File

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