lemmur-app-android/lib/pages/media_view.dart

158 lines
5.3 KiB
Dart
Raw Normal View History

2021-02-08 12:15:48 +01:00
import 'dart:math' show max, min;
2021-10-21 14:40:28 +02:00
import 'package:extended_image/extended_image.dart';
2020-09-10 23:03:50 +02:00
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
2020-09-10 23:03:50 +02:00
import 'package:flutter_hooks/flutter_hooks.dart';
2021-02-08 12:15:48 +01:00
import 'package:matrix4_transform/matrix4_transform.dart';
2020-09-10 23:03:50 +02:00
import 'package:photo_view/photo_view.dart';
import '../util/icons.dart';
2021-03-18 19:24:29 +01:00
import '../util/share.dart';
import '../widgets/bottom_modal.dart';
2020-09-30 19:05:00 +02:00
/// View to interact with a media object. Zoom in/out, download, share, etc.
class MediaViewPage extends HookWidget {
2020-09-10 23:03:50 +02:00
final String url;
2021-02-08 12:15:48 +01:00
static const yThreshold = 150;
static const speedThreshold = 45;
2020-09-10 23:03:50 +02:00
2021-03-10 08:34:30 +01:00
const MediaViewPage(this.url);
2020-09-10 23:03:50 +02:00
@override
Widget build(BuildContext context) {
final showButtons = useState(true);
final isZoomedOut = useState(true);
2021-02-08 12:15:48 +01:00
final scaleIsInitial = useState(true);
final isDragging = useState(false);
final offset = useState(Offset.zero);
final prevOffset = usePrevious(offset.value) ?? Offset.zero;
2020-09-10 23:03:50 +02:00
notImplemented() {
2021-03-10 08:34:30 +01:00
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("this feature hasn't been implemented yet 😰")));
}
2021-02-08 12:15:48 +01:00
// TODO: hide navbar and topbar on android without a content jump
2020-09-11 10:54:03 +02:00
2021-03-20 15:50:49 +01:00
sharePhoto() {
2021-02-09 15:12:13 +01:00
showBottomModal(
context: context,
2021-02-09 15:12:13 +01:00
builder: (context) => Column(
children: [
ListTile(
leading: const Icon(Icons.link),
title: const Text('Share link'),
onTap: () {
Navigator.of(context).pop();
2021-03-20 15:50:49 +01:00
share(url, context: context);
2021-02-09 15:12:13 +01:00
},
),
ListTile(
leading: const Icon(Icons.image),
title: const Text('Share file'),
onTap: () {
Navigator.of(context).pop();
notImplemented();
// TODO: share file
},
),
],
),
);
}
2020-09-10 23:03:50 +02:00
return Scaffold(
extendBodyBehindAppBar: true,
extendBody: true,
2021-02-08 12:15:48 +01:00
backgroundColor:
Colors.black.withOpacity(max(0, 1.0 - (offset.value.dy.abs() / 200))),
2020-09-11 11:00:04 +02:00
appBar: showButtons.value
? AppBar(
systemOverlayStyle: SystemUiOverlayStyle.light,
2021-02-24 20:52:18 +01:00
iconTheme: const IconThemeData(color: Colors.white),
2020-09-11 11:00:04 +02:00
backgroundColor: Colors.black38,
2021-01-03 19:43:39 +01:00
leading: const CloseButton(),
2020-09-11 11:00:04 +02:00
actions: [
2020-09-10 23:03:50 +02:00
IconButton(
icon: Icon(shareIcon),
2020-09-10 23:03:50 +02:00
tooltip: 'share',
2021-03-20 15:50:49 +01:00
onPressed: sharePhoto,
2020-09-10 23:03:50 +02:00
),
IconButton(
2021-01-03 19:43:39 +01:00
icon: const Icon(Icons.file_download),
2020-09-10 23:03:50 +02:00
tooltip: 'download',
onPressed: notImplemented,
2020-09-10 23:03:50 +02:00
),
2020-09-11 11:00:04 +02:00
],
)
: null,
2021-02-08 12:15:48 +01:00
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();
2021-02-08 12:15:48 +01:00
} else {
offset.value = Offset.zero;
}
}
2021-02-08 12:15:48 +01:00
: (_) {
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,
2021-10-21 14:40:28 +02:00
imageProvider: ExtendedNetworkImageProvider(url, cache: true),
2021-02-08 12:15:48 +01:00
heroAttributes: PhotoViewHeroAttributes(tag: url),
loadingBuilder: (context, event) =>
const Center(child: CircularProgressIndicator.adaptive()),
2021-02-08 12:15:48 +01:00
),
2020-09-10 23:03:50 +02:00
),
),
);
}
}