fullscreen: show low res image until high res is loaded, fixed hero animation
This commit is contained in:
parent
1b5d2a96d5
commit
adfc93a59c
9 changed files with 284 additions and 82 deletions
|
@ -5,6 +5,7 @@ import 'package:aves/model/metadata_service.dart';
|
||||||
import 'package:aves/utils/change_notifier.dart';
|
import 'package:aves/utils/change_notifier.dart';
|
||||||
import 'package:aves/utils/time_utils.dart';
|
import 'package:aves/utils/time_utils.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geocoder/geocoder.dart';
|
import 'package:geocoder/geocoder.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -118,14 +119,15 @@ class ImageEntry {
|
||||||
|
|
||||||
bool get canRotate => canEdit && (mimeType == MimeTypes.MIME_JPEG || mimeType == MimeTypes.MIME_PNG);
|
bool get canRotate => canEdit && (mimeType == MimeTypes.MIME_JPEG || mimeType == MimeTypes.MIME_PNG);
|
||||||
|
|
||||||
double get aspectRatio {
|
bool get rotated => ((isVideo && isCatalogued) ? catalogMetadata.videoRotation : orientationDegrees) % 180 == 90;
|
||||||
|
|
||||||
|
double get displayAspectRatio {
|
||||||
if (width == 0 || height == 0) return 1;
|
if (width == 0 || height == 0) return 1;
|
||||||
if (isVideo && isCatalogued) {
|
return rotated ? height / width : width / height;
|
||||||
if (catalogMetadata.videoRotation % 180 == 90) return height / width;
|
|
||||||
}
|
|
||||||
return width / height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Size get displaySize => rotated ? Size(height.toDouble(), width.toDouble()) : Size(width.toDouble(), height.toDouble());
|
||||||
|
|
||||||
int get megaPixels => width != null && height != null ? (width * height / 1000000).round() : null;
|
int get megaPixels => width != null && height != null ? (width * height / 1000000).round() : null;
|
||||||
|
|
||||||
DateTime get bestDate {
|
DateTime get bestDate {
|
||||||
|
|
|
@ -249,17 +249,7 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(OMIcons.whatshot),
|
leading: const Icon(OMIcons.whatshot),
|
||||||
title: const Text('Debug'),
|
title: const Text('Debug'),
|
||||||
onTap: () {
|
onTap: () => _goToDebug(context),
|
||||||
Navigator.pop(context);
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => DebugPage(
|
|
||||||
source: source,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -285,6 +275,18 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _goToDebug(BuildContext context) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => DebugPage(
|
||||||
|
source: source,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FilteredCollectionNavTile extends StatelessWidget {
|
class _FilteredCollectionNavTile extends StatelessWidget {
|
||||||
|
@ -311,21 +313,23 @@ class _FilteredCollectionNavTile extends StatelessWidget {
|
||||||
leading: leading,
|
leading: leading,
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
dense: dense,
|
dense: dense,
|
||||||
onTap: () {
|
onTap: () => _goToCollection(context),
|
||||||
Navigator.pushAndRemoveUntil(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => CollectionPage(CollectionLens(
|
|
||||||
source: source,
|
|
||||||
filters: [filter],
|
|
||||||
groupFactor: settings.collectionGroupFactor,
|
|
||||||
sortFactor: settings.collectionSortFactor,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
(route) => false,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _goToCollection(BuildContext context) {
|
||||||
|
Navigator.pushAndRemoveUntil(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => CollectionPage(CollectionLens(
|
||||||
|
source: source,
|
||||||
|
filters: [filter],
|
||||||
|
groupFactor: settings.collectionGroupFactor,
|
||||||
|
sortFactor: settings.collectionSortFactor,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
(route) => false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ class GridThumbnail extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
key: ValueKey(entry.uri),
|
key: ValueKey(entry.uri),
|
||||||
onTap: () => _showFullscreen(context),
|
onTap: () => _goToFullscreen(context),
|
||||||
child: Selector<MediaQueryData, double>(
|
child: Selector<MediaQueryData, double>(
|
||||||
selector: (c, mq) => mq.size.width,
|
selector: (c, mq) => mq.size.width,
|
||||||
builder: (c, mqWidth, child) {
|
builder: (c, mqWidth, child) {
|
||||||
|
@ -102,7 +102,7 @@ class GridThumbnail extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showFullscreen(BuildContext context) {
|
void _goToFullscreen(BuildContext context) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
TransparentMaterialPageRoute(
|
TransparentMaterialPageRoute(
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
||||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||||
|
import 'package:aves/widgets/common/transition_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
@ -59,17 +60,11 @@ class Thumbnail extends StatelessWidget {
|
||||||
? image
|
? image
|
||||||
: Hero(
|
: Hero(
|
||||||
tag: heroTag,
|
tag: heroTag,
|
||||||
flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
|
flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) {
|
||||||
// use LayoutBuilder only during hero animation
|
return TransitionImage(
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
image: provider,
|
||||||
final dim = min(constraints.maxWidth, constraints.maxHeight);
|
animation: animation,
|
||||||
return Image(
|
);
|
||||||
image: provider,
|
|
||||||
width: dim,
|
|
||||||
height: dim,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: image,
|
child: image,
|
||||||
);
|
);
|
||||||
|
|
191
lib/widgets/common/transition_image.dart
Normal file
191
lib/widgets/common/transition_image.dart
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// adapted from `RawImage`, `paintImage()` from `DecorationImagePainter`, etc.
|
||||||
|
// to transition between 2 different fits during hero animation:
|
||||||
|
// - BoxFit.cover at t=0
|
||||||
|
// - BoxFit.contain at t=1
|
||||||
|
|
||||||
|
class TransitionImage extends StatefulWidget {
|
||||||
|
final ImageProvider image;
|
||||||
|
final double width, height;
|
||||||
|
final ValueListenable<double> animation;
|
||||||
|
final gaplessPlayback = false;
|
||||||
|
|
||||||
|
const TransitionImage({
|
||||||
|
@required this.image,
|
||||||
|
@required this.animation,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TransitionImageState createState() => _TransitionImageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TransitionImageState extends State<TransitionImage> {
|
||||||
|
ImageStream _imageStream;
|
||||||
|
ImageInfo _imageInfo;
|
||||||
|
bool _isListeningToStream = false;
|
||||||
|
int _frameNumber;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
assert(_imageStream != null);
|
||||||
|
_stopListeningToStream();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
_resolveImage();
|
||||||
|
|
||||||
|
if (TickerMode.of(context)) {
|
||||||
|
_listenToStream();
|
||||||
|
} else {
|
||||||
|
_stopListeningToStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(TransitionImage oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (_isListeningToStream) {
|
||||||
|
_imageStream.removeListener(_getListener());
|
||||||
|
_imageStream.addListener(_getListener());
|
||||||
|
}
|
||||||
|
if (widget.image != oldWidget.image) _resolveImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reassemble() {
|
||||||
|
_resolveImage(); // in case the image cache was flushed
|
||||||
|
super.reassemble();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resolveImage() {
|
||||||
|
final provider = widget.image;
|
||||||
|
final newStream = provider.resolve(createLocalImageConfiguration(
|
||||||
|
context,
|
||||||
|
size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,
|
||||||
|
));
|
||||||
|
assert(newStream != null);
|
||||||
|
_updateSourceStream(newStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageStreamListener _getListener() {
|
||||||
|
return ImageStreamListener(
|
||||||
|
_handleImageFrame,
|
||||||
|
onChunk: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
|
||||||
|
setState(() {
|
||||||
|
_imageInfo = imageInfo;
|
||||||
|
_frameNumber = _frameNumber == null ? 0 : _frameNumber + 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates _imageStream to newStream, and moves the stream listener
|
||||||
|
// registration from the old stream to the new stream (if a listener was
|
||||||
|
// registered).
|
||||||
|
void _updateSourceStream(ImageStream newStream) {
|
||||||
|
if (_imageStream?.key == newStream?.key) return;
|
||||||
|
|
||||||
|
if (_isListeningToStream) _imageStream.removeListener(_getListener());
|
||||||
|
|
||||||
|
if (!widget.gaplessPlayback) {
|
||||||
|
setState(() {
|
||||||
|
_imageInfo = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_frameNumber = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
_imageStream = newStream;
|
||||||
|
if (_isListeningToStream) _imageStream.addListener(_getListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listenToStream() {
|
||||||
|
if (_isListeningToStream) return;
|
||||||
|
_imageStream.addListener(_getListener());
|
||||||
|
_isListeningToStream = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopListeningToStream() {
|
||||||
|
if (!_isListeningToStream) return;
|
||||||
|
_imageStream.removeListener(_getListener());
|
||||||
|
_isListeningToStream = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ValueListenableBuilder<double>(
|
||||||
|
valueListenable: widget.animation,
|
||||||
|
builder: (context, t, child) => CustomPaint(
|
||||||
|
painter: _TransitionImagePainter(
|
||||||
|
// AssetImage(name).resolve(configuration)
|
||||||
|
image: _imageInfo?.image,
|
||||||
|
scale: _imageInfo?.scale ?? 1.0,
|
||||||
|
t: t,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TransitionImagePainter extends CustomPainter {
|
||||||
|
final ui.Image image;
|
||||||
|
final double scale;
|
||||||
|
final double t;
|
||||||
|
|
||||||
|
const _TransitionImagePainter({
|
||||||
|
@required this.image,
|
||||||
|
@required this.scale,
|
||||||
|
@required this.t,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..isAntiAlias = false
|
||||||
|
..filterQuality = FilterQuality.low;
|
||||||
|
const alignment = Alignment.center;
|
||||||
|
|
||||||
|
final rect = ui.Rect.fromLTWH(0, 0, size.width, size.height);
|
||||||
|
final inputSize = Size(image.width.toDouble(), image.height.toDouble());
|
||||||
|
final outputSize = rect.size;
|
||||||
|
|
||||||
|
final coverSizes = applyBoxFit(BoxFit.cover, inputSize / scale, size);
|
||||||
|
final containSizes = applyBoxFit(BoxFit.contain, inputSize / scale, size);
|
||||||
|
final sourceSize = Size.lerp(coverSizes.source, containSizes.source, t) * scale;
|
||||||
|
final destinationSize = Size.lerp(coverSizes.destination, containSizes.destination, t);
|
||||||
|
|
||||||
|
final halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0;
|
||||||
|
final halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0;
|
||||||
|
final dx = halfWidthDelta + alignment.x * halfWidthDelta;
|
||||||
|
final dy = halfHeightDelta + alignment.y * halfHeightDelta;
|
||||||
|
final destinationPosition = rect.topLeft.translate(dx, dy);
|
||||||
|
final destinationRect = destinationPosition & destinationSize;
|
||||||
|
final sourceRect = alignment.inscribe(
|
||||||
|
sourceSize,
|
||||||
|
Offset.zero & inputSize,
|
||||||
|
);
|
||||||
|
canvas.drawImageRect(image, sourceRect, destinationRect, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CustomPainter oldDelegate) => true;
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
|
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
||||||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||||
import 'package:aves/widgets/fullscreen/video_view.dart';
|
import 'package:aves/widgets/fullscreen/video_view.dart';
|
||||||
|
@ -28,6 +29,8 @@ class ImageView extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const backgroundDecoration = BoxDecoration(color: Colors.transparent);
|
const backgroundDecoration = BoxDecoration(color: Colors.transparent);
|
||||||
|
|
||||||
|
// no hero for videos, as a typical video first frame is different from its thumbnail
|
||||||
|
|
||||||
if (entry.isVideo) {
|
if (entry.isVideo) {
|
||||||
final videoController = videoControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
|
final videoController = videoControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
|
||||||
return PhotoView.customChild(
|
return PhotoView.customChild(
|
||||||
|
@ -38,7 +41,6 @@ class ImageView extends StatelessWidget {
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
backgroundDecoration: backgroundDecoration,
|
backgroundDecoration: backgroundDecoration,
|
||||||
// no hero as most videos fullscreen image is different from its thumbnail
|
|
||||||
scaleStateChangedCallback: onScaleChanged,
|
scaleStateChangedCallback: onScaleChanged,
|
||||||
minScale: PhotoViewComputedScale.contained,
|
minScale: PhotoViewComputedScale.contained,
|
||||||
initialScale: PhotoViewComputedScale.contained,
|
initialScale: PhotoViewComputedScale.contained,
|
||||||
|
@ -46,53 +48,61 @@ class ImageView extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final placeholderBuilder = (context) => const Center(
|
// if the hero tag is defined in the `loadingBuilder` and also set by the `heroAttributes`,
|
||||||
child: SizedBox(
|
// the route transition becomes visible if the final is loaded before the hero animation is done.
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final heroAttributes = heroTag != null
|
|
||||||
? PhotoViewHeroAttributes(
|
|
||||||
tag: heroTag,
|
|
||||||
transitionOnUserGestures: true,
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
|
// if the hero tag wraps the whole `PhotoView` and the `loadingBuilder` is not provided,
|
||||||
|
// there's a black frame between the hero animation and the final image, even when it's cached.
|
||||||
|
|
||||||
|
final loadingBuilder = (context) => Image(
|
||||||
|
image: ThumbnailProvider(
|
||||||
|
entry: entry,
|
||||||
|
extent: Constants.thumbnailCacheExtent,
|
||||||
|
),
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget child;
|
||||||
if (entry.isSvg) {
|
if (entry.isSvg) {
|
||||||
return PhotoView.customChild(
|
child = PhotoView.customChild(
|
||||||
child: SvgPicture(
|
child: SvgPicture(
|
||||||
UriPicture(
|
UriPicture(
|
||||||
uri: entry.uri,
|
uri: entry.uri,
|
||||||
mimeType: entry.mimeType,
|
mimeType: entry.mimeType,
|
||||||
colorFilter: Constants.svgColorFilter,
|
colorFilter: Constants.svgColorFilter,
|
||||||
),
|
),
|
||||||
placeholderBuilder: placeholderBuilder,
|
placeholderBuilder: loadingBuilder,
|
||||||
),
|
),
|
||||||
backgroundDecoration: backgroundDecoration,
|
backgroundDecoration: backgroundDecoration,
|
||||||
heroAttributes: heroAttributes,
|
|
||||||
scaleStateChangedCallback: onScaleChanged,
|
scaleStateChangedCallback: onScaleChanged,
|
||||||
minScale: PhotoViewComputedScale.contained,
|
minScale: PhotoViewComputedScale.contained,
|
||||||
initialScale: PhotoViewComputedScale.contained,
|
initialScale: PhotoViewComputedScale.contained,
|
||||||
onTapUp: (tapContext, details, value) => onTap?.call(),
|
onTapUp: (tapContext, details, value) => onTap?.call(),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
child = PhotoView(
|
||||||
|
// key includes size and orientation to refresh when the image is rotated
|
||||||
|
key: ValueKey('${entry.orientationDegrees}_${entry.width}_${entry.height}_${entry.path}'),
|
||||||
|
imageProvider: UriImage(
|
||||||
|
uri: entry.uri,
|
||||||
|
mimeType: entry.mimeType,
|
||||||
|
),
|
||||||
|
loadingBuilder: (context, event) => loadingBuilder(context),
|
||||||
|
backgroundDecoration: backgroundDecoration,
|
||||||
|
scaleStateChangedCallback: onScaleChanged,
|
||||||
|
minScale: PhotoViewComputedScale.contained,
|
||||||
|
initialScale: PhotoViewComputedScale.contained,
|
||||||
|
onTapUp: (tapContext, details, value) => onTap?.call(),
|
||||||
|
filterQuality: FilterQuality.low,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PhotoView(
|
return heroTag != null
|
||||||
// key includes size and orientation to refresh when the image is rotated
|
? Hero(
|
||||||
key: ValueKey('${entry.orientationDegrees}_${entry.width}_${entry.height}_${entry.path}'),
|
tag: heroTag,
|
||||||
imageProvider: UriImage(uri: entry.uri, mimeType: entry.mimeType),
|
transitionOnUserGestures: true,
|
||||||
loadingBuilder: (context, event) => placeholderBuilder(context),
|
child: child,
|
||||||
backgroundDecoration: backgroundDecoration,
|
)
|
||||||
heroAttributes: heroAttributes,
|
: child;
|
||||||
scaleStateChangedCallback: onScaleChanged,
|
|
||||||
minScale: PhotoViewComputedScale.contained,
|
|
||||||
initialScale: PhotoViewComputedScale.contained,
|
|
||||||
onTapUp: (tapContext, details, value) => onTap?.call(),
|
|
||||||
filterQuality: FilterQuality.low,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,14 +75,14 @@ class InfoPageState extends State<InfoPage> {
|
||||||
entry: entry,
|
entry: entry,
|
||||||
showTitle: !locationAtTop,
|
showTitle: !locationAtTop,
|
||||||
visibleNotifier: widget.visibleNotifier,
|
visibleNotifier: widget.visibleNotifier,
|
||||||
onFilter: _goToFilteredCollection,
|
onFilter: _goToCollection,
|
||||||
);
|
);
|
||||||
final basicAndLocationSliver = locationAtTop
|
final basicAndLocationSliver = locationAtTop
|
||||||
? SliverToBoxAdapter(
|
? SliverToBoxAdapter(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: BasicSection(entry: entry, collection: collection, onFilter: _goToFilteredCollection)),
|
Expanded(child: BasicSection(entry: entry, collection: collection, onFilter: _goToCollection)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: locationSection),
|
Expanded(child: locationSection),
|
||||||
],
|
],
|
||||||
|
@ -91,7 +91,7 @@ class InfoPageState extends State<InfoPage> {
|
||||||
: SliverList(
|
: SliverList(
|
||||||
delegate: SliverChildListDelegate.fixed(
|
delegate: SliverChildListDelegate.fixed(
|
||||||
[
|
[
|
||||||
BasicSection(entry: entry, collection: collection, onFilter: _goToFilteredCollection),
|
BasicSection(entry: entry, collection: collection, onFilter: _goToCollection),
|
||||||
locationSection,
|
locationSection,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -153,7 +153,7 @@ class InfoPageState extends State<InfoPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToFilteredCollection(CollectionFilter filter) {
|
void _goToCollection(CollectionFilter filter) {
|
||||||
if (collection == null) return;
|
if (collection == null) return;
|
||||||
Navigator.pushAndRemoveUntil(
|
Navigator.pushAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -64,7 +64,7 @@ class AvesVideoState extends State<AvesVideo> {
|
||||||
}
|
}
|
||||||
return Center(
|
return Center(
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: entry.aspectRatio,
|
aspectRatio: entry.displayAspectRatio,
|
||||||
child: VideoPlayer(widget.controller),
|
child: VideoPlayer(widget.controller),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -223,7 +223,7 @@ class StatsPage extends StatelessWidget {
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: AvesFilterChip(
|
child: AvesFilterChip(
|
||||||
filter: filterBuilder(label),
|
filter: filterBuilder(label),
|
||||||
onPressed: (filter) => _goToFilteredCollection(context, filter),
|
onPressed: (filter) => _goToCollection(context, filter),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
LinearPercentIndicator(
|
LinearPercentIndicator(
|
||||||
|
@ -253,7 +253,7 @@ class StatsPage extends StatelessWidget {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToFilteredCollection(BuildContext context, CollectionFilter filter) {
|
void _goToCollection(BuildContext context, CollectionFilter filter) {
|
||||||
if (collection == null) return;
|
if (collection == null) return;
|
||||||
Navigator.pushAndRemoveUntil(
|
Navigator.pushAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
|
|
Loading…
Reference in a new issue