From 4b9625afea99ba28590cee1e5728d47f77e5fce5 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 26 Feb 2020 15:42:47 +0900 Subject: [PATCH] fullscreen: reimplemented PhotoViewGallery to force rebuild after rotation --- lib/model/image_entry.dart | 5 - .../fullscreen_action_delegate.dart | 2 +- lib/widgets/fullscreen/fullscreen_page.dart | 8 ++ lib/widgets/fullscreen/image_page.dart | 94 +++++++++++-------- 4 files changed, 62 insertions(+), 47 deletions(-) diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index a7eb992c7..ae7f58c05 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -1,12 +1,9 @@ -import 'dart:io'; - import 'package:aves/model/image_file_service.dart'; import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/metadata_service.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/time_utils.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:geocoder/geocoder.dart'; import 'package:path/path.dart'; import 'package:tuple/tuple.dart'; @@ -232,8 +229,6 @@ class ImageEntry { final orientationDegrees = newFields['orientationDegrees']; if (orientationDegrees is int) this.orientationDegrees = orientationDegrees; - // TODO TLAD move cache eviction out of ImageEntry and into ImagePage together with `imageChangeNotifier` handling - await FileImage(File(this.path)).evict(); imageChangeNotifier.notifyListeners(); return true; } diff --git a/lib/widgets/fullscreen/fullscreen_action_delegate.dart b/lib/widgets/fullscreen/fullscreen_action_delegate.dart index fddb2bf03..503fd7d17 100644 --- a/lib/widgets/fullscreen/fullscreen_action_delegate.dart +++ b/lib/widgets/fullscreen/fullscreen_action_delegate.dart @@ -86,7 +86,7 @@ class FullscreenActionDelegate { Future _rotate(BuildContext context, ImageEntry entry, {@required bool clockwise}) async { final success = await entry.rotate(clockwise: clockwise); - _showFeedback(context, success ? 'Done!' : 'Failed'); + if (!success) _showFeedback(context, 'Failed'); } Future _showDeleteDialog(BuildContext context, ImageEntry entry) async { diff --git a/lib/widgets/fullscreen/fullscreen_page.dart b/lib/widgets/fullscreen/fullscreen_page.dart index c55afa5fd..bc326da19 100644 --- a/lib/widgets/fullscreen/fullscreen_page.dart +++ b/lib/widgets/fullscreen/fullscreen_page.dart @@ -322,16 +322,24 @@ class _FullscreenVerticalPageViewState extends State void _registerWidget(FullscreenVerticalPageView widget) { widget.verticalPager.addListener(_onVerticalPageControllerChange); + widget.entry.imageChangeNotifier.addListener(_onImageChange); } void _unregisterWidget(FullscreenVerticalPageView widget) { widget.verticalPager.removeListener(_onVerticalPageControllerChange); + widget.entry.imageChangeNotifier.removeListener(_onImageChange); } void _onVerticalPageControllerChange() { _backgroundColorNotifier.value = _backgroundColorNotifier.value.withOpacity(min(1.0, widget.verticalPager.page)); } + void _onImageChange() async { + await FileImage(File(widget.entry.path)).evict(); + // rebuild to refresh the Image inside ImagePage + setState(() {}); + } + @override Widget build(BuildContext context) { return ValueListenableBuilder( diff --git a/lib/widgets/fullscreen/image_page.dart b/lib/widgets/fullscreen/image_page.dart index d0bb29b2f..013148111 100644 --- a/lib/widgets/fullscreen/image_page.dart +++ b/lib/widgets/fullscreen/image_page.dart @@ -5,7 +5,6 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/widgets/fullscreen/video.dart'; import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:photo_view/photo_view_gallery.dart'; import 'package:tuple/tuple.dart'; import 'package:video_player/video_player.dart'; @@ -36,51 +35,64 @@ class ImagePageState extends State with AutomaticKeepAliveClientMixin @override Widget build(BuildContext context) { super.build(context); - return PhotoViewGallery.builder( - itemCount: entries.length, - builder: (context, index) { - final entry = entries[index]; - if (entry.isVideo) { - final videoController = widget.videoControllers.firstWhere((kv) => kv.item1 == entry.path, orElse: () => null)?.item2; - return PhotoViewGalleryPageOptions.customChild( - child: videoController != null - ? AvesVideo( - entry: entry, - controller: videoController, - ) - : const SizedBox(), - // no hero as most videos fullscreen image is different from its thumbnail + + const scrollDirection = Axis.horizontal; + const backgroundDecoration = BoxDecoration(color: Colors.transparent); + final scaleStateChangedCallback = widget.onScaleChanged; + + return PhotoViewGestureDetectorScope( + axis: scrollDirection, + child: PageView.builder( + controller: widget.pageController, + onPageChanged: widget.onPageChanged, + itemCount: entries.length, + itemBuilder: (context, index) { + final entry = entries[index]; + if (entry.isVideo) { + final videoController = widget.videoControllers.firstWhere((kv) => kv.item1 == entry.path, orElse: () => null)?.item2; + return PhotoView.customChild( + child: videoController != null + ? AvesVideo( + entry: entry, + controller: videoController, + ) + : const SizedBox(), + backgroundDecoration: backgroundDecoration, + // no hero as most videos fullscreen image is different from its thumbnail + scaleStateChangedCallback: scaleStateChangedCallback, + minScale: PhotoViewComputedScale.contained, + initialScale: PhotoViewComputedScale.contained, + onTapUp: (tapContext, details, value) => widget.onTap?.call(), + ); + } + return PhotoView( + // key includes size and orientation to refresh when the image is rotated + key: ValueKey('${entry.orientationDegrees}_${entry.width}_${entry.height}_${entry.path}'), + imageProvider: FileImage(File(entry.path)), + loadingBuilder: (context, event) => const Center( + child: SizedBox( + width: 64, + height: 64, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ), + ), + backgroundDecoration: backgroundDecoration, + heroAttributes: PhotoViewHeroAttributes( + tag: entry.uri, + transitionOnUserGestures: true, + ), + scaleStateChangedCallback: scaleStateChangedCallback, minScale: PhotoViewComputedScale.contained, initialScale: PhotoViewComputedScale.contained, onTapUp: (tapContext, details, value) => widget.onTap?.call(), + filterQuality: FilterQuality.low, ); - } - return PhotoViewGalleryPageOptions( - imageProvider: FileImage(File(entry.path)), - heroAttributes: PhotoViewHeroAttributes( - tag: entry.uri, - transitionOnUserGestures: true, - ), - minScale: PhotoViewComputedScale.contained, - initialScale: PhotoViewComputedScale.contained, - onTapUp: (tapContext, details, value) => widget.onTap?.call(), - filterQuality: FilterQuality.low, - ); - }, - loadingBuilder: (context, event) => const Center( - child: SizedBox( - width: 64, - height: 64, - child: CircularProgressIndicator( - strokeWidth: 2, - ), - ), + }, + scrollDirection: scrollDirection, + physics: const BouncingScrollPhysics(), ), - backgroundDecoration: BoxDecoration(color: Colors.transparent), - pageController: widget.pageController, - onPageChanged: widget.onPageChanged, - scaleStateChangedCallback: widget.onScaleChanged, - scrollPhysics: const BouncingScrollPhysics(), ); }