Media View improvements (#128)

This commit is contained in:
Filip Krawczyk 2021-02-08 12:15:48 +01:00 committed by GitHub
parent 79fb42785c
commit 430d196997
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 36 deletions

View File

@ -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 ## v0.2.2 - 2021-02-03
Minimum Lemmy version supported: `v0.9.4` Minimum Lemmy version supported: `v0.9.4`

View File

@ -1,8 +1,10 @@
import 'dart:math' show max, min;
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:matrix4_transform/matrix4_transform.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import '../widgets/bottom_modal.dart'; import '../widgets/bottom_modal.dart';
@ -11,6 +13,8 @@ import '../widgets/bottom_modal.dart';
class MediaViewPage extends HookWidget { class MediaViewPage extends HookWidget {
final String url; final String url;
final GlobalKey<ScaffoldState> _key = GlobalKey(); final GlobalKey<ScaffoldState> _key = GlobalKey();
static const yThreshold = 150;
static const speedThreshold = 45;
MediaViewPage(this.url); MediaViewPage(this.url);
@ -18,30 +22,19 @@ class MediaViewPage extends HookWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final showButtons = useState(true); final showButtons = useState(true);
final isZoomedOut = 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() { notImplemented() {
_key.currentState.showSnackBar(const SnackBar( _key.currentState.showSnackBar(const SnackBar(
content: Text("this feature hasn't been implemented yet 😰"))); content: Text("this feature hasn't been implemented yet 😰")));
} }
useEffect(() { // TODO: hide navbar and topbar on android without a content jump
if (showButtons.value) {
SystemChrome.setEnabledSystemUIOverlays([
SystemUiOverlay.bottom,
SystemUiOverlay.top,
]);
} else {
SystemChrome.setEnabledSystemUIOverlays([]);
}
return null;
}, [showButtons.value]);
useEffect(
() => () => SystemChrome.setEnabledSystemUIOverlays([
SystemUiOverlay.bottom,
SystemUiOverlay.top,
]),
[]);
share() { share() {
showModalBottomSheet( showModalBottomSheet(
@ -77,6 +70,8 @@ class MediaViewPage extends HookWidget {
key: _key, key: _key,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
extendBody: true, extendBody: true,
backgroundColor:
Colors.black.withOpacity(max(0, 1.0 - (offset.value.dy.abs() / 200))),
appBar: showButtons.value appBar: showButtons.value
? AppBar( ? AppBar(
backgroundColor: Colors.black38, backgroundColor: Colors.black38,
@ -96,26 +91,67 @@ class MediaViewPage extends HookWidget {
], ],
) )
: null, : null,
body: GestureDetector( body: Listener(
onTapUp: (details) => showButtons.value = !showButtons.value, onPointerMove: scaleIsInitial.value
onVerticalDragEnd: isZoomedOut.value ? (event) {
? (details) { if (!isDragging.value &&
if (details.primaryVelocity.abs() > 1000) { 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(); Navigator.of(context).pop();
} else {
offset.value = Offset.zero;
} }
} }
: null, : (_) {
child: PhotoView( offset.value = Offset.zero;
scaleStateChangedCallback: (value) { isDragging.value = false;
isZoomedOut.value = value == PhotoViewScaleState.zoomedOut || },
value == PhotoViewScaleState.initial; child: AnimatedContainer(
}, transform: Matrix4Transform()
minScale: PhotoViewComputedScale.contained, .scale(max(0.9, 1 - offset.value.dy.abs() / 1000))
initialScale: PhotoViewComputedScale.contained, .translateOffset(offset.value)
imageProvider: CachedNetworkImageProvider(url), .rotate(min(-offset.value.dx / 2000, 0.1))
heroAttributes: PhotoViewHeroAttributes(tag: url), .matrix4,
loadingBuilder: (context, event) => duration: isDragging.value
const Center(child: CircularProgressIndicator()), ? 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()),
),
), ),
), ),
); );

View File

@ -67,6 +67,7 @@ void goToMedia(BuildContext context, String url) => Navigator.push(
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (_, __, ___) => MediaViewPage(url), pageBuilder: (_, __, ___) => MediaViewPage(url),
transitionDuration: const Duration(milliseconds: 300), transitionDuration: const Duration(milliseconds: 300),
opaque: false,
transitionsBuilder: (_, animation, __, child) => transitionsBuilder: (_, animation, __, child) =>
FadeTransition(opacity: animation, child: child), FadeTransition(opacity: animation, child: child),
), ),

View File

@ -282,6 +282,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.10-nullsafety.1" 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: meta:
dependency: transitive dependency: transitive
description: description:

View File

@ -44,6 +44,8 @@ dependencies:
timeago: ^2.0.27 timeago: ^2.0.27
fuzzy: <1.0.0 fuzzy: <1.0.0
lemmy_api_client: ^0.10.2 lemmy_api_client: ^0.10.2
matrix4_transform: ^1.1.7
flutter: flutter:
sdk: flutter sdk: flutter