mirror of
https://github.com/immich-app/immich.git
synced 2026-02-28 09:38:43 +03:00
The existing implementation for showing asset details uses a bottom sheet, and is not in sync with the preview or scroll intent. Other apps use inline details, which is much cleaner and feels better to use.
674 lines
24 KiB
Dart
674 lines
24 KiB
Dart
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';
|
|
import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart';
|
|
import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart';
|
|
import 'package:immich_mobile/widgets/photo_view/src/photo_view_wrappers.dart';
|
|
import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart';
|
|
|
|
export 'src/controller/photo_view_controller.dart';
|
|
export 'src/controller/photo_view_scalestate_controller.dart';
|
|
export 'src/core/photo_view_gesture_detector.dart' show PhotoViewGestureDetectorScope, PhotoViewPageViewScrollPhysics;
|
|
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:
|
|
///
|
|
/// ```
|
|
/// PhotoView(
|
|
/// imageProvider: imageProvider,
|
|
/// loadingBuilder: (context, progress) => Center(
|
|
/// child: Container(
|
|
/// width: 20.0,
|
|
/// height: 20.0,
|
|
/// child: CircularProgressIndicator(
|
|
/// value: _progress == null
|
|
/// ? null
|
|
/// : _progress.cumulativeBytesLoaded /
|
|
/// _progress.expectedTotalBytes,
|
|
/// ),
|
|
/// ),
|
|
/// ),
|
|
/// backgroundDecoration: BoxDecoration(color: Colors.black),
|
|
/// gaplessPlayback: false,
|
|
/// customSize: MediaQuery.of(context).size,
|
|
/// heroAttributes: const HeroAttributes(
|
|
/// tag: "someTag",
|
|
/// transitionOnUserGestures: true,
|
|
/// ),
|
|
/// scaleStateChangedCallback: this.onScaleStateChanged,
|
|
/// enableRotation: true,
|
|
/// controller: controller,
|
|
/// minScale: PhotoViewComputedScale.contained * 0.8,
|
|
/// maxScale: PhotoViewComputedScale.covered * 1.8,
|
|
/// initialScale: PhotoViewComputedScale.contained,
|
|
/// basePosition: Alignment.center,
|
|
/// scaleStateCycle: scaleStateCycle
|
|
/// );
|
|
/// ```
|
|
///
|
|
/// You can customize to show an custom child instead of an image:
|
|
///
|
|
/// ```
|
|
/// PhotoView.customChild(
|
|
/// child: Container(
|
|
/// width: 220.0,
|
|
/// height: 250.0,
|
|
/// child: const Text(
|
|
/// "Hello there, this is a text",
|
|
/// )
|
|
/// ),
|
|
/// childSize: const Size(220.0, 250.0),
|
|
/// backgroundDecoration: BoxDecoration(color: Colors.black),
|
|
/// gaplessPlayback: false,
|
|
/// customSize: MediaQuery.of(context).size,
|
|
/// heroAttributes: const HeroAttributes(
|
|
/// tag: "someTag",
|
|
/// transitionOnUserGestures: true,
|
|
/// ),
|
|
/// scaleStateChangedCallback: this.onScaleStateChanged,
|
|
/// enableRotation: true,
|
|
/// controller: controller,
|
|
/// minScale: PhotoViewComputedScale.contained * 0.8,
|
|
/// maxScale: PhotoViewComputedScale.covered * 1.8,
|
|
/// initialScale: PhotoViewComputedScale.contained,
|
|
/// basePosition: Alignment.center,
|
|
/// scaleStateCycle: scaleStateCycle
|
|
/// );
|
|
/// ```
|
|
/// The [maxScale], [minScale] and [initialScale] options may be [double] or a [PhotoViewComputedScale] constant
|
|
///
|
|
/// Sample using [maxScale], [minScale] and [initialScale]
|
|
///
|
|
/// ```
|
|
/// PhotoView(
|
|
/// imageProvider: imageProvider,
|
|
/// minScale: PhotoViewComputedScale.contained * 0.8,
|
|
/// maxScale: PhotoViewComputedScale.covered * 1.8,
|
|
/// initialScale: PhotoViewComputedScale.contained * 1.1,
|
|
/// );
|
|
/// ```
|
|
///
|
|
/// [customSize] is used to define the viewPort size in which the image will be
|
|
/// scaled to. This argument is rarely used. By default is the size that this widget assumes.
|
|
///
|
|
/// The argument [gaplessPlayback] 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`.
|
|
///
|
|
/// To use within an hero animation, specify [heroAttributes]. When
|
|
/// [heroAttributes] is specified, the image provider retrieval process should
|
|
/// be sync.
|
|
///
|
|
/// Sample using hero animation:
|
|
/// ```
|
|
/// // screen1
|
|
/// ...
|
|
/// Hero(
|
|
/// tag: "someTag",
|
|
/// child: Image.asset(
|
|
/// "assets/large-image.jpg",
|
|
/// width: 150.0
|
|
/// ),
|
|
/// )
|
|
/// // screen2
|
|
/// ...
|
|
/// child: PhotoView(
|
|
/// imageProvider: AssetImage("assets/large-image.jpg"),
|
|
/// heroAttributes: const HeroAttributes(tag: "someTag"),
|
|
/// )
|
|
/// ```
|
|
///
|
|
/// **Note: If you don't want to the zoomed image do not overlaps the size of the container, use [ClipRect](https://docs.flutter.io/flutter/widgets/ClipRect-class.html)**
|
|
///
|
|
/// ## Controllers
|
|
///
|
|
/// Controllers, when specified to PhotoView widget, enables the author(you) to listen for state updates through a `Stream` and change those values externally.
|
|
///
|
|
/// While [PhotoViewScaleStateController] is only responsible for the `scaleState`, [PhotoViewController] is responsible for all fields os [PhotoViewControllerValue].
|
|
///
|
|
/// To use them, pass a instance of those items on [controller] or [scaleStateController];
|
|
///
|
|
/// Since those follows the standard controller pattern found in widgets like [PageView] and [ScrollView], whoever instantiates it, should [dispose] it afterwards.
|
|
///
|
|
/// Example of [controller] usage, only listening for state changes:
|
|
///
|
|
/// ```
|
|
/// class _ExampleWidgetState extends State<ExampleWidget> {
|
|
///
|
|
/// PhotoViewController controller;
|
|
/// double scaleCopy;
|
|
///
|
|
/// @override
|
|
/// void initState() {
|
|
/// super.initState();
|
|
/// controller = PhotoViewController()
|
|
/// ..outputStateStream.listen(listener);
|
|
/// }
|
|
///
|
|
/// @override
|
|
/// void dispose() {
|
|
/// controller.dispose();
|
|
/// super.dispose();
|
|
/// }
|
|
///
|
|
/// void listener(PhotoViewControllerValue value){
|
|
/// setState((){
|
|
/// scaleCopy = value.scale;
|
|
/// })
|
|
/// }
|
|
///
|
|
/// @override
|
|
/// Widget build(BuildContext context) {
|
|
/// return Stack(
|
|
/// children: <Widget>[
|
|
/// Positioned.fill(
|
|
/// child: PhotoView(
|
|
/// imageProvider: AssetImage("assets/pudim.png"),
|
|
/// controller: controller,
|
|
/// );
|
|
/// ),
|
|
/// Text("Scale applied: $scaleCopy")
|
|
/// ],
|
|
/// );
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// An example of [scaleStateController] with state changes:
|
|
/// ```
|
|
/// class _ExampleWidgetState extends State<ExampleWidget> {
|
|
///
|
|
/// PhotoViewScaleStateController scaleStateController;
|
|
///
|
|
/// @override
|
|
/// void initState() {
|
|
/// super.initState();
|
|
/// scaleStateController = PhotoViewScaleStateController();
|
|
/// }
|
|
///
|
|
/// @override
|
|
/// void dispose() {
|
|
/// scaleStateController.dispose();
|
|
/// super.dispose();
|
|
/// }
|
|
///
|
|
/// void goBack(){
|
|
/// scaleStateController.scaleState = PhotoViewScaleState.originalSize;
|
|
/// }
|
|
///
|
|
/// @override
|
|
/// Widget build(BuildContext context) {
|
|
/// return Stack(
|
|
/// children: <Widget>[
|
|
/// Positioned.fill(
|
|
/// child: PhotoView(
|
|
/// imageProvider: AssetImage("assets/pudim.png"),
|
|
/// scaleStateController: scaleStateController,
|
|
/// );
|
|
/// ),
|
|
/// FlatButton(
|
|
/// child: Text("Go to original size"),
|
|
/// onPressed: goBack,
|
|
/// );
|
|
/// ],
|
|
/// );
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
class PhotoView extends StatefulWidget {
|
|
/// Creates a widget that displays a zoomable image.
|
|
///
|
|
/// To show an image from the network or from an asset bundle, use their respective
|
|
/// image providers, ie: [AssetImage] or [NetworkImage]
|
|
///
|
|
/// Internally, the image is rendered within an [Image] widget.
|
|
const PhotoView({
|
|
super.key,
|
|
required this.imageProvider,
|
|
required this.index,
|
|
this.loadingBuilder,
|
|
this.backgroundDecoration,
|
|
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,
|
|
this.minScale,
|
|
this.initialScale,
|
|
this.basePosition,
|
|
this.scaleStateCycle,
|
|
this.onTapUp,
|
|
this.onTapDown,
|
|
this.onDragStart,
|
|
this.onDragEnd,
|
|
this.onDragUpdate,
|
|
this.onDragCancel,
|
|
this.onScaleEnd,
|
|
this.onLongPressStart,
|
|
this.customSize,
|
|
this.gestureDetectorBehavior,
|
|
this.tightMode,
|
|
this.filterQuality,
|
|
this.disableGestures,
|
|
this.disableScaleGestures,
|
|
this.errorBuilder,
|
|
this.enablePanAlways,
|
|
}) : child = null,
|
|
childSize = null;
|
|
|
|
/// Creates a widget that displays a zoomable child.
|
|
///
|
|
/// It has been created to resemble [PhotoView] behavior within widgets that aren't an image, such as [Container], [Text] or a svg.
|
|
///
|
|
/// Instead of a [imageProvider], this constructor will receive a [child] and a [childSize].
|
|
///
|
|
const PhotoView.customChild({
|
|
super.key,
|
|
required this.child,
|
|
this.childSize,
|
|
this.backgroundDecoration,
|
|
this.wantKeepAlive = false,
|
|
this.heroAttributes,
|
|
this.onPageBuild,
|
|
this.controllerCallbackBuilder,
|
|
this.scaleStateChangedCallback,
|
|
this.enableRotation = false,
|
|
this.controller,
|
|
this.scaleStateController,
|
|
this.maxScale,
|
|
this.minScale,
|
|
this.initialScale,
|
|
this.basePosition,
|
|
this.scaleStateCycle,
|
|
this.onTapUp,
|
|
this.onTapDown,
|
|
this.onDragStart,
|
|
this.onDragEnd,
|
|
this.onDragUpdate,
|
|
this.onDragCancel,
|
|
this.onScaleEnd,
|
|
this.onLongPressStart,
|
|
this.customSize,
|
|
this.gestureDetectorBehavior,
|
|
this.tightMode,
|
|
this.filterQuality,
|
|
this.disableScaleGestures,
|
|
this.disableGestures,
|
|
this.enablePanAlways,
|
|
}) : semanticLabel = null,
|
|
errorBuilder = null,
|
|
imageProvider = null,
|
|
gaplessPlayback = false,
|
|
loadingBuilder = null,
|
|
index = 0;
|
|
|
|
/// Given a [imageProvider] it resolves into an zoomable image widget using. It
|
|
/// is required
|
|
final ImageProvider? imageProvider;
|
|
|
|
/// While [imageProvider] is not resolved, [loadingBuilder] is called by [PhotoView]
|
|
/// into the screen, by default it is a centered [CircularProgressIndicator]
|
|
final LoadingBuilder? loadingBuilder;
|
|
|
|
/// Show loadFailedChild when the image failed to load
|
|
final ImageErrorWidgetBuilder? errorBuilder;
|
|
|
|
/// Changes the background behind image, defaults to `Colors.black`.
|
|
final BoxDecoration? backgroundDecoration;
|
|
|
|
/// This is used to keep the state of an image in the gallery (e.g. scale state).
|
|
/// `false` -> resets the state (default)
|
|
/// `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`.
|
|
final bool gaplessPlayback;
|
|
|
|
/// Attributes that are going to be passed to [PhotoViewCore]'s
|
|
/// [Hero]. Leave this property undefined if you don't want a hero animation.
|
|
final PhotoViewHeroAttributes? heroAttributes;
|
|
|
|
/// Defines the size of the scaling base of the image inside [PhotoView],
|
|
/// 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;
|
|
|
|
/// A flag that enables the rotation gesture support
|
|
final bool enableRotation;
|
|
|
|
/// The specified custom child to be shown instead of a image
|
|
final Widget? child;
|
|
|
|
/// The size of the custom [child]. [PhotoView] uses this value to compute the relation between the child and the container's size to calculate the scale value.
|
|
final Size? childSize;
|
|
|
|
/// Defines the maximum size in which the image will be allowed to assume, it
|
|
/// is proportional to the original image size. Can be either a double (absolute value) or a
|
|
/// [PhotoViewComputedScale], that can be multiplied by a double
|
|
final dynamic maxScale;
|
|
|
|
/// Defines the minimum size in which the image will be allowed to assume, it
|
|
/// is proportional to the original image size. Can be either a double (absolute value) or a
|
|
/// [PhotoViewComputedScale], that can be multiplied by a double
|
|
final dynamic minScale;
|
|
|
|
/// Defines the initial size in which the image will be assume in the mounting of the component, it
|
|
/// is proportional to the original image size. Can be either a double (absolute value) or a
|
|
/// [PhotoViewComputedScale], that can be multiplied by a double
|
|
final dynamic initialScale;
|
|
|
|
/// A way to control PhotoView transformation factors externally and listen to its updates
|
|
final PhotoViewControllerBase? controller;
|
|
|
|
/// A way to control PhotoViewScaleState value externally and listen to its updates
|
|
final PhotoViewScaleStateController? scaleStateController;
|
|
|
|
/// The alignment of the scale origin in relation to the widget size. Default is [Alignment.center]
|
|
final Alignment? basePosition;
|
|
|
|
/// Defines de next [PhotoViewScaleState] given the actual one. Default is [defaultScaleStateCycle]
|
|
final ScaleStateCycle? scaleStateCycle;
|
|
|
|
/// A pointer that will trigger a tap has stopped contacting the screen at a
|
|
/// particular location.
|
|
final PhotoViewImageTapUpCallback? onTapUp;
|
|
|
|
/// A pointer that might cause a tap has contacted the screen at a particular
|
|
/// location.
|
|
final PhotoViewImageTapDownCallback? onTapDown;
|
|
|
|
/// A pointer that might cause a tap has contacted the screen at a particular
|
|
/// location.
|
|
final PhotoViewImageDragStartCallback? onDragStart;
|
|
|
|
/// A pointer that might cause a tap has contacted the screen at a particular
|
|
/// location.
|
|
final PhotoViewImageDragEndCallback? onDragEnd;
|
|
|
|
/// A pointer that might cause a tap has contacted the screen at a particular
|
|
/// location.
|
|
final PhotoViewImageDragUpdateCallback? onDragUpdate;
|
|
|
|
/// A callback when a drag gesture is canceled by the system.
|
|
final VoidCallback? onDragCancel;
|
|
|
|
/// A pointer that will trigger a scale has stopped contacting the screen at a
|
|
/// particular location.
|
|
final PhotoViewImageScaleEndCallback? onScaleEnd;
|
|
|
|
/// A pointer that might cause a tap has contacted the screen at a particular
|
|
/// location.
|
|
final PhotoViewImageLongPressStartCallback? onLongPressStart;
|
|
|
|
/// [HitTestBehavior] to be passed to the internal gesture detector.
|
|
final HitTestBehavior? gestureDetectorBehavior;
|
|
|
|
/// Enables tight mode, making background container assume the size of the image/child.
|
|
/// Useful when inside a [Dialog]
|
|
final bool? tightMode;
|
|
|
|
/// Quality levels for image filters.
|
|
final FilterQuality? filterQuality;
|
|
|
|
// Removes gesture detector if `true`.
|
|
// 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;
|
|
|
|
final int index;
|
|
|
|
bool get _isCustomChild {
|
|
return child != null;
|
|
}
|
|
|
|
@override
|
|
State<StatefulWidget> createState() {
|
|
return _PhotoViewState();
|
|
}
|
|
}
|
|
|
|
class _PhotoViewState extends State<PhotoView> with AutomaticKeepAliveClientMixin {
|
|
// image retrieval
|
|
|
|
// controller
|
|
late bool _controlledController;
|
|
late PhotoViewControllerBase _controller;
|
|
late bool _controlledScaleStateController;
|
|
late PhotoViewScaleStateController _scaleStateController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
if (widget.controller == null) {
|
|
_controlledController = true;
|
|
_controller = PhotoViewController();
|
|
widget.onPageBuild?.call(_controller);
|
|
} else {
|
|
_controlledController = false;
|
|
_controller = widget.controller!;
|
|
}
|
|
|
|
if (widget.scaleStateController == null) {
|
|
_controlledScaleStateController = true;
|
|
_scaleStateController = PhotoViewScaleStateController();
|
|
} else {
|
|
_controlledScaleStateController = false;
|
|
_scaleStateController = widget.scaleStateController!;
|
|
}
|
|
|
|
_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
|
|
void didUpdateWidget(PhotoView oldWidget) {
|
|
if (widget.controller == null) {
|
|
if (!_controlledController) {
|
|
_controlledController = true;
|
|
_controller = PhotoViewController();
|
|
widget.onPageBuild?.call(_controller);
|
|
}
|
|
} else {
|
|
_controlledController = false;
|
|
_controller = widget.controller!;
|
|
}
|
|
|
|
if (widget.scaleStateController == null) {
|
|
if (!_controlledScaleStateController) {
|
|
_controlledScaleStateController = true;
|
|
_scaleStateController = PhotoViewScaleStateController();
|
|
}
|
|
} else {
|
|
_controlledScaleStateController = false;
|
|
_scaleStateController = widget.scaleStateController!;
|
|
}
|
|
super.didUpdateWidget(oldWidget);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
if (_controlledController) {
|
|
_controller.dispose();
|
|
}
|
|
if (_controlledScaleStateController) {
|
|
_scaleStateController.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
void scaleStateListener(PhotoViewScaleState scaleState) {
|
|
if (widget.scaleStateChangedCallback != null) {
|
|
widget.scaleStateChangedCallback!(_scaleStateController.scaleState);
|
|
}
|
|
}
|
|
|
|
PhotoViewControllerBase _controllerGetter() => _controller;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
return LayoutBuilder(
|
|
builder: (BuildContext context, BoxConstraints constraints) {
|
|
final computedOuterSize = widget.customSize ?? constraints.biggest;
|
|
final backgroundDecoration = widget.backgroundDecoration ?? const BoxDecoration(color: Colors.transparent);
|
|
|
|
return widget._isCustomChild
|
|
? CustomChildWrapper(
|
|
childSize: widget.childSize,
|
|
backgroundDecoration: backgroundDecoration,
|
|
heroAttributes: widget.heroAttributes,
|
|
scaleStateChangedCallback: widget.scaleStateChangedCallback,
|
|
enableRotation: widget.enableRotation,
|
|
controller: _controller,
|
|
scaleStateController: _scaleStateController,
|
|
maxScale: widget.maxScale,
|
|
minScale: widget.minScale,
|
|
initialScale: widget.initialScale,
|
|
basePosition: widget.basePosition,
|
|
scaleStateCycle: widget.scaleStateCycle,
|
|
onTapUp: widget.onTapUp,
|
|
onTapDown: widget.onTapDown,
|
|
onDragStart: widget.onDragStart,
|
|
onDragEnd: widget.onDragEnd,
|
|
onDragUpdate: widget.onDragUpdate,
|
|
onDragCancel: widget.onDragCancel,
|
|
onScaleEnd: widget.onScaleEnd,
|
|
onLongPressStart: widget.onLongPressStart,
|
|
outerSize: computedOuterSize,
|
|
gestureDetectorBehavior: widget.gestureDetectorBehavior,
|
|
tightMode: widget.tightMode,
|
|
filterQuality: widget.filterQuality,
|
|
disableGestures: widget.disableGestures,
|
|
disableScaleGestures: widget.disableScaleGestures,
|
|
enablePanAlways: widget.enablePanAlways,
|
|
child: widget.child,
|
|
)
|
|
: ImageWrapper(
|
|
imageProvider: widget.imageProvider!,
|
|
loadingBuilder: widget.loadingBuilder,
|
|
backgroundDecoration: backgroundDecoration,
|
|
semanticLabel: widget.semanticLabel,
|
|
gaplessPlayback: widget.gaplessPlayback,
|
|
heroAttributes: widget.heroAttributes,
|
|
scaleStateChangedCallback: widget.scaleStateChangedCallback,
|
|
enableRotation: widget.enableRotation,
|
|
controller: _controller,
|
|
scaleStateController: _scaleStateController,
|
|
maxScale: widget.maxScale,
|
|
minScale: widget.minScale,
|
|
initialScale: widget.initialScale,
|
|
basePosition: widget.basePosition,
|
|
scaleStateCycle: widget.scaleStateCycle,
|
|
onTapUp: widget.onTapUp,
|
|
onTapDown: widget.onTapDown,
|
|
onDragStart: widget.onDragStart,
|
|
onDragEnd: widget.onDragEnd,
|
|
onDragUpdate: widget.onDragUpdate,
|
|
onDragCancel: widget.onDragCancel,
|
|
onScaleEnd: widget.onScaleEnd,
|
|
onLongPressStart: widget.onLongPressStart,
|
|
outerSize: computedOuterSize,
|
|
gestureDetectorBehavior: widget.gestureDetectorBehavior,
|
|
tightMode: widget.tightMode,
|
|
filterQuality: widget.filterQuality,
|
|
disableGestures: widget.disableGestures,
|
|
disableScaleGestures: widget.disableScaleGestures,
|
|
errorBuilder: widget.errorBuilder,
|
|
enablePanAlways: widget.enablePanAlways,
|
|
index: widget.index,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool get wantKeepAlive => widget.wantKeepAlive;
|
|
}
|
|
|
|
/// The default [ScaleStateCycle]
|
|
PhotoViewScaleState defaultScaleStateCycle(PhotoViewScaleState actual) => switch (actual) {
|
|
PhotoViewScaleState.initial => PhotoViewScaleState.covering,
|
|
PhotoViewScaleState.covering => PhotoViewScaleState.originalSize,
|
|
PhotoViewScaleState.originalSize => PhotoViewScaleState.initial,
|
|
PhotoViewScaleState.zoomedIn || PhotoViewScaleState.zoomedOut => PhotoViewScaleState.initial,
|
|
};
|
|
|
|
/// A type definition for a [Function] that receives the actual [PhotoViewScaleState] and returns the next one
|
|
/// It is used internally to walk in the "doubletap gesture cycle".
|
|
/// It is passed to [PhotoView.scaleStateCycle]
|
|
typedef ScaleStateCycle = PhotoViewScaleState Function(PhotoViewScaleState actual);
|
|
|
|
/// A type definition for a callback when the user taps up the photoview region
|
|
typedef PhotoViewImageTapUpCallback =
|
|
Function(BuildContext context, TapUpDetails details, PhotoViewControllerValue controllerValue);
|
|
|
|
/// A type definition for a callback when the user taps down the photoview region
|
|
typedef PhotoViewImageTapDownCallback =
|
|
Function(BuildContext context, TapDownDetails details, PhotoViewControllerValue controllerValue);
|
|
|
|
/// A type definition for a callback when the user drags up
|
|
typedef PhotoViewImageDragStartCallback =
|
|
Function(
|
|
BuildContext context,
|
|
DragStartDetails details,
|
|
PhotoViewControllerBase controllerValue,
|
|
PhotoViewScaleStateController scaleStateController,
|
|
);
|
|
|
|
/// A type definition for a callback when the user drags
|
|
typedef PhotoViewImageDragUpdateCallback =
|
|
Function(BuildContext context, DragUpdateDetails details, PhotoViewControllerValue controllerValue);
|
|
|
|
/// A type definition for a callback when the user taps down the photoview region
|
|
typedef PhotoViewImageDragEndCallback =
|
|
Function(BuildContext context, DragEndDetails details, PhotoViewControllerValue controllerValue);
|
|
|
|
/// A type definition for a callback when a user finished scale
|
|
typedef PhotoViewImageScaleEndCallback =
|
|
Function(BuildContext context, ScaleEndDetails details, PhotoViewControllerValue controllerValue);
|
|
|
|
/// A type definition for a callback when the user long press start
|
|
typedef PhotoViewImageLongPressStartCallback =
|
|
Function(BuildContext context, LongPressStartDetails details, PhotoViewControllerValue controllerValue);
|
|
|
|
/// A type definition for a callback to show a widget while the image is loading, a [ImageChunkEvent] is passed to inform progress
|
|
typedef LoadingBuilder = Widget Function(BuildContext context, ImageChunkEvent? event, int index);
|