diff --git a/CHANGELOG.md b/CHANGELOG.md index eee16bf..ae65544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### Changed + +- Changed image viewer dismissal to be more fun. The image now also moves on the x axis, changes scale and rotates a bit for more user enjoyment + ## v0.2.2 - 2021-02-03 Minimum Lemmy version supported: `v0.9.4` diff --git a/lib/pages/media_view.dart b/lib/pages/media_view.dart index 20f0cf8..e8e1dbc 100644 --- a/lib/pages/media_view.dart +++ b/lib/pages/media_view.dart @@ -1,8 +1,10 @@ +import 'dart:math' show max, min; + import 'package:cached_network_image/cached_network_image.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:matrix4_transform/matrix4_transform.dart'; import 'package:photo_view/photo_view.dart'; import '../widgets/bottom_modal.dart'; @@ -11,6 +13,8 @@ import '../widgets/bottom_modal.dart'; class MediaViewPage extends HookWidget { final String url; final GlobalKey _key = GlobalKey(); + static const yThreshold = 150; + static const speedThreshold = 45; MediaViewPage(this.url); @@ -18,30 +22,19 @@ class MediaViewPage extends HookWidget { Widget build(BuildContext context) { final showButtons = useState(true); final isZoomedOut = useState(true); + final scaleIsInitial = useState(true); + + final isDragging = useState(false); + + final offset = useState(Offset.zero); + final prevOffset = usePrevious(offset.value); notImplemented() { _key.currentState.showSnackBar(const SnackBar( content: Text("this feature hasn't been implemented yet 😰"))); } - useEffect(() { - if (showButtons.value) { - SystemChrome.setEnabledSystemUIOverlays([ - SystemUiOverlay.bottom, - SystemUiOverlay.top, - ]); - } else { - SystemChrome.setEnabledSystemUIOverlays([]); - } - return null; - }, [showButtons.value]); - - useEffect( - () => () => SystemChrome.setEnabledSystemUIOverlays([ - SystemUiOverlay.bottom, - SystemUiOverlay.top, - ]), - []); + // TODO: hide navbar and topbar on android without a content jump share() { showModalBottomSheet( @@ -77,6 +70,8 @@ class MediaViewPage extends HookWidget { key: _key, extendBodyBehindAppBar: true, extendBody: true, + backgroundColor: + Colors.black.withOpacity(max(0, 1.0 - (offset.value.dy.abs() / 200))), appBar: showButtons.value ? AppBar( backgroundColor: Colors.black38, @@ -96,26 +91,67 @@ class MediaViewPage extends HookWidget { ], ) : null, - body: GestureDetector( - onTapUp: (details) => showButtons.value = !showButtons.value, - onVerticalDragEnd: isZoomedOut.value - ? (details) { - if (details.primaryVelocity.abs() > 1000) { + body: Listener( + onPointerMove: scaleIsInitial.value + ? (event) { + if (!isDragging.value && + event.delta.dx.abs() > event.delta.dy.abs()) return; + isDragging.value = true; + offset.value += event.delta; + } + : (_) => isDragging.value = false, + onPointerCancel: (_) => offset.value = Offset.zero, + onPointerUp: isZoomedOut.value + ? (_) { + if (!isDragging.value) { + showButtons.value = !showButtons.value; + return; + } + + isDragging.value = false; + final speed = (offset.value - prevOffset).distance; + if (speed > speedThreshold || + offset.value.dy.abs() > yThreshold) { Navigator.of(context).pop(); + } else { + offset.value = Offset.zero; } } - : null, - child: PhotoView( - scaleStateChangedCallback: (value) { - isZoomedOut.value = value == PhotoViewScaleState.zoomedOut || - value == PhotoViewScaleState.initial; - }, - minScale: PhotoViewComputedScale.contained, - initialScale: PhotoViewComputedScale.contained, - imageProvider: CachedNetworkImageProvider(url), - heroAttributes: PhotoViewHeroAttributes(tag: url), - loadingBuilder: (context, event) => - const Center(child: CircularProgressIndicator()), + : (_) { + offset.value = Offset.zero; + isDragging.value = false; + }, + child: AnimatedContainer( + transform: Matrix4Transform() + .scale(max(0.9, 1 - offset.value.dy.abs() / 1000)) + .translateOffset(offset.value) + .rotate(min(-offset.value.dx / 2000, 0.1)) + .matrix4, + duration: isDragging.value + ? Duration.zero + : const Duration(milliseconds: 200), + child: PhotoView( + backgroundDecoration: + const BoxDecoration(color: Colors.transparent), + scaleStateChangedCallback: (value) { + isZoomedOut.value = value == PhotoViewScaleState.zoomedOut || + value == PhotoViewScaleState.initial; + showButtons.value = isZoomedOut.value; + + scaleIsInitial.value = value == PhotoViewScaleState.initial; + isDragging.value = false; + offset.value = Offset.zero; + }, + onTapUp: isZoomedOut.value + ? null + : (_, __, ___) => showButtons.value = !showButtons.value, + minScale: PhotoViewComputedScale.contained, + initialScale: PhotoViewComputedScale.contained, + imageProvider: CachedNetworkImageProvider(url), + heroAttributes: PhotoViewHeroAttributes(tag: url), + loadingBuilder: (context, event) => + const Center(child: CircularProgressIndicator()), + ), ), ), ); diff --git a/lib/util/goto.dart b/lib/util/goto.dart index a894f68..7972ee9 100644 --- a/lib/util/goto.dart +++ b/lib/util/goto.dart @@ -67,6 +67,7 @@ void goToMedia(BuildContext context, String url) => Navigator.push( PageRouteBuilder( pageBuilder: (_, __, ___) => MediaViewPage(url), transitionDuration: const Duration(milliseconds: 300), + opaque: false, transitionsBuilder: (_, animation, __, child) => FadeTransition(opacity: animation, child: child), ), diff --git a/pubspec.lock b/pubspec.lock index 142e0f1..bd4dbdb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -282,6 +282,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.12.10-nullsafety.1" + matrix4_transform: + dependency: "direct main" + description: + name: matrix4_transform + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e5e3080..8975655 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,8 @@ dependencies: timeago: ^2.0.27 fuzzy: <1.0.0 lemmy_api_client: ^0.10.2 + matrix4_transform: ^1.1.7 + flutter: sdk: flutter