From 7f44d508dc08f09729ba301eef5c2983c91087c1 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey <10599762+fyfrey@users.noreply.github.com> Date: Thu, 22 Jun 2023 04:13:23 +0200 Subject: [PATCH] feat(mobile): pinch to zoom on asset grid (#2905) --- .../home/ui/asset_grid/immich_asset_grid.dart | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart index 45f5b2ad4e..b628273bf6 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart @@ -1,3 +1,6 @@ +import 'dart:math'; + +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -51,6 +54,12 @@ class ImmichAssetGrid extends HookConsumerWidget { 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 @@ -80,22 +89,43 @@ class ImmichAssetGrid extends HookConsumerWidget { onWillPop: onWillPop, child: HeroMode( enabled: enableHeroAnimations.value, - child: ImmichAssetGridView( - onRefresh: onRefresh, - assetsPerRow: assetsPerRow ?? - settings.getSetting(AppSettingsEnum.tilesPerRow), - 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, + child: 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, + ), ), ), ); @@ -113,3 +143,11 @@ class ImmichAssetGrid extends HookConsumerWidget { ); } } + +/// accepts a gesture even though it should reject it (because child won) +class CustomScaleGestureRecognizer extends ScaleGestureRecognizer { + @override + void rejectGesture(int pointer) { + acceptGesture(pointer); + } +}