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:
parent
863e983726
commit
f9739c9730
@ -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,
|
||||
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -31,7 +31,7 @@ class MemoryLane extends HookConsumerWidget {
|
||||
onTap: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
AutoRouter.of(context).push(
|
||||
VerticalRouteView(
|
||||
MemoryRoute(
|
||||
memories: memories,
|
||||
memoryIndex: index,
|
||||
),
|
||||
|
@ -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}';
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user