feat(mobile): sqlite asset viewer (#19552)
* add full image provider and refactor thumb providers * photo_view updates * wip: asset-viewer * fix controller dispose on page change * wip: bottom sheet * fix interactions * more bottomsheet changes * generate schema * PR feedback * refactor asset viewer * never rotate and fix background on page change * use photoview as the loading builder * precache after delay * claude: optimizing rebuild of image provider * claude: optimizing image decoding and caching * use proper cache for new full size image providers * chore: load local HEIC fullsize for iOS * make controller callbacks nullable * remove imageprovider cache * do not handle drag gestures when zoomed * use loadOriginal setting for HEIC / larger images * preload assets outside timer * never use same controllers in photo-view gallery * fix: cannot scroll down once swipe with bottom sheet --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:immich_mobile/widgets/photo_view/src/controller/photo_view_controller.dart';
|
||||
import 'package:immich_mobile/widgets/photo_view/src/controller/photo_view_scalestate_controller.dart';
|
||||
import 'package:immich_mobile/widgets/photo_view/src/core/photo_view_core.dart';
|
||||
@@ -16,6 +15,11 @@ export 'src/photo_view_computed_scale.dart';
|
||||
export 'src/photo_view_scale_state.dart';
|
||||
export 'src/utils/photo_view_hero_attributes.dart';
|
||||
|
||||
typedef PhotoViewControllerCallback = PhotoViewControllerBase Function();
|
||||
typedef PhotoViewControllerCallbackBuilder = void Function(
|
||||
PhotoViewControllerCallback photoViewMethod,
|
||||
);
|
||||
|
||||
/// A [StatefulWidget] that contains all the photo view rendering elements.
|
||||
///
|
||||
/// Sample code to use within an image:
|
||||
@@ -239,8 +243,11 @@ class PhotoView extends StatefulWidget {
|
||||
this.wantKeepAlive = false,
|
||||
this.gaplessPlayback = false,
|
||||
this.heroAttributes,
|
||||
this.onPageBuild,
|
||||
this.controllerCallbackBuilder,
|
||||
this.scaleStateChangedCallback,
|
||||
this.enableRotation = false,
|
||||
this.semanticLabel,
|
||||
this.controller,
|
||||
this.scaleStateController,
|
||||
this.maxScale,
|
||||
@@ -260,6 +267,7 @@ class PhotoView extends StatefulWidget {
|
||||
this.tightMode,
|
||||
this.filterQuality,
|
||||
this.disableGestures,
|
||||
this.disableScaleGestures,
|
||||
this.errorBuilder,
|
||||
this.enablePanAlways,
|
||||
}) : child = null,
|
||||
@@ -278,6 +286,8 @@ class PhotoView extends StatefulWidget {
|
||||
this.backgroundDecoration,
|
||||
this.wantKeepAlive = false,
|
||||
this.heroAttributes,
|
||||
this.onPageBuild,
|
||||
this.controllerCallbackBuilder,
|
||||
this.scaleStateChangedCallback,
|
||||
this.enableRotation = false,
|
||||
this.controller,
|
||||
@@ -298,9 +308,11 @@ class PhotoView extends StatefulWidget {
|
||||
this.gestureDetectorBehavior,
|
||||
this.tightMode,
|
||||
this.filterQuality,
|
||||
this.disableScaleGestures,
|
||||
this.disableGestures,
|
||||
this.enablePanAlways,
|
||||
}) : errorBuilder = null,
|
||||
}) : semanticLabel = null,
|
||||
errorBuilder = null,
|
||||
imageProvider = null,
|
||||
gaplessPlayback = false,
|
||||
loadingBuilder = null,
|
||||
@@ -325,6 +337,11 @@ class PhotoView extends StatefulWidget {
|
||||
/// `true` -> keeps the state
|
||||
final bool wantKeepAlive;
|
||||
|
||||
/// A Semantic description of the image.
|
||||
///
|
||||
/// Used to provide a description of the image to TalkBack on Android, and VoiceOver on iOS.
|
||||
final String? semanticLabel;
|
||||
|
||||
/// This is used to continue showing the old image (`true`), or briefly show
|
||||
/// nothing (`false`), when the `imageProvider` changes. By default it's set
|
||||
/// to `false`.
|
||||
@@ -338,6 +355,12 @@ class PhotoView extends StatefulWidget {
|
||||
/// by default it is `MediaQuery.of(context).size`.
|
||||
final Size? customSize;
|
||||
|
||||
// Called when a new PhotoView widget is built
|
||||
final ValueChanged<PhotoViewControllerBase>? onPageBuild;
|
||||
|
||||
// Called from the parent during page change to get the new controller
|
||||
final PhotoViewControllerCallbackBuilder? controllerCallbackBuilder;
|
||||
|
||||
/// A [Function] to be called whenever the scaleState changes, this happens when the user double taps the content ou start to pinch-in.
|
||||
final ValueChanged<PhotoViewScaleState>? scaleStateChangedCallback;
|
||||
|
||||
@@ -419,6 +442,9 @@ class PhotoView extends StatefulWidget {
|
||||
// Useful when custom gesture detector is used in child widget.
|
||||
final bool? disableGestures;
|
||||
|
||||
/// Mirror to [PhotoView.disableGestures]
|
||||
final bool? disableScaleGestures;
|
||||
|
||||
/// Enable pan the widget even if it's smaller than the hole parent widget.
|
||||
/// Useful when you want to drag a widget without restrictions.
|
||||
final bool? enablePanAlways;
|
||||
@@ -452,6 +478,7 @@ class _PhotoViewState extends State<PhotoView>
|
||||
if (widget.controller == null) {
|
||||
_controlledController = true;
|
||||
_controller = PhotoViewController();
|
||||
widget.onPageBuild?.call(_controller);
|
||||
} else {
|
||||
_controlledController = false;
|
||||
_controller = widget.controller!;
|
||||
@@ -466,6 +493,8 @@ class _PhotoViewState extends State<PhotoView>
|
||||
}
|
||||
|
||||
_scaleStateController.outputScaleStateStream.listen(scaleStateListener);
|
||||
// Pass a ref to the method back to the gallery so it can fetch the controller on page changes
|
||||
widget.controllerCallbackBuilder?.call(_controllerGetter);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -474,6 +503,7 @@ class _PhotoViewState extends State<PhotoView>
|
||||
if (!_controlledController) {
|
||||
_controlledController = true;
|
||||
_controller = PhotoViewController();
|
||||
widget.onPageBuild?.call(_controller);
|
||||
}
|
||||
} else {
|
||||
_controlledController = false;
|
||||
@@ -509,6 +539,8 @@ class _PhotoViewState extends State<PhotoView>
|
||||
}
|
||||
}
|
||||
|
||||
PhotoViewControllerBase _controllerGetter() => _controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
@@ -547,6 +579,7 @@ class _PhotoViewState extends State<PhotoView>
|
||||
tightMode: widget.tightMode,
|
||||
filterQuality: widget.filterQuality,
|
||||
disableGestures: widget.disableGestures,
|
||||
disableScaleGestures: widget.disableScaleGestures,
|
||||
enablePanAlways: widget.enablePanAlways,
|
||||
child: widget.child,
|
||||
)
|
||||
@@ -554,6 +587,7 @@ class _PhotoViewState extends State<PhotoView>
|
||||
imageProvider: widget.imageProvider!,
|
||||
loadingBuilder: widget.loadingBuilder,
|
||||
backgroundDecoration: backgroundDecoration,
|
||||
semanticLabel: widget.semanticLabel,
|
||||
gaplessPlayback: widget.gaplessPlayback,
|
||||
heroAttributes: widget.heroAttributes,
|
||||
scaleStateChangedCallback: widget.scaleStateChangedCallback,
|
||||
@@ -577,6 +611,7 @@ class _PhotoViewState extends State<PhotoView>
|
||||
tightMode: widget.tightMode,
|
||||
filterQuality: widget.filterQuality,
|
||||
disableGestures: widget.disableGestures,
|
||||
disableScaleGestures: widget.disableScaleGestures,
|
||||
errorBuilder: widget.errorBuilder,
|
||||
enablePanAlways: widget.enablePanAlways,
|
||||
index: widget.index,
|
||||
@@ -626,6 +661,7 @@ typedef PhotoViewImageDragStartCallback = Function(
|
||||
BuildContext context,
|
||||
DragStartDetails details,
|
||||
PhotoViewControllerValue controllerValue,
|
||||
PhotoViewScaleStateController scaleStateController,
|
||||
);
|
||||
|
||||
/// A type definition for a callback when the user drags
|
||||
|
||||
Reference in New Issue
Block a user