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 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,
|
||||||
|
@ -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(),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user