From 2176c654f13dbd377156c363399132720182c2bd Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Fri, 3 Apr 2020 14:21:16 +0900 Subject: [PATCH] overlay: bottom overlay scrolling along vertical axis --- lib/widgets/fullscreen/fullscreen_body.dart | 133 ++++++++++++-------- 1 file changed, 80 insertions(+), 53 deletions(-) diff --git a/lib/widgets/fullscreen/fullscreen_body.dart b/lib/widgets/fullscreen/fullscreen_body.dart index 55fad160d..c010279c5 100644 --- a/lib/widgets/fullscreen/fullscreen_body.dart +++ b/lib/widgets/fullscreen/fullscreen_body.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/utils/change_notifier.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'; @@ -40,6 +41,7 @@ class FullscreenBodyState extends State with SingleTickerProvide int _currentHorizontalPage; ValueNotifier _currentVerticalPage; PageController _horizontalPager, _verticalPager; + final AChangeNotifier _verticalScrollNotifier = AChangeNotifier(); final ValueNotifier _overlayVisible = ValueNotifier(true); AnimationController _overlayAnimationController; Animation _topOverlayScale, _bottomOverlayScale; @@ -69,7 +71,7 @@ class FullscreenBodyState extends State with SingleTickerProvide _currentHorizontalPage = max(0, entries.indexOf(_entry)); _currentVerticalPage = ValueNotifier(imagePage); _horizontalPager = PageController(initialPage: _currentHorizontalPage); - _verticalPager = PageController(initialPage: _currentVerticalPage.value); + _verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChange); _overlayAnimationController = AnimationController( duration: const Duration(milliseconds: 400), vsync: this, @@ -110,6 +112,7 @@ class FullscreenBodyState extends State with SingleTickerProvide _overlayAnimationController.dispose(); _overlayVisible.removeListener(_onOverlayVisibleChange); _videoControllers.forEach((kv) => kv.item2.dispose()); + _verticalPager.removeListener(_onVerticalPageControllerChange); _unregisterWidget(widget); super.dispose(); } @@ -124,6 +127,76 @@ class FullscreenBodyState extends State with SingleTickerProvide @override Widget build(BuildContext context) { + final topOverlay = ValueListenableBuilder( + valueListenable: _currentVerticalPage, + builder: (context, page, child) { + final showOverlay = _entry != null && page == imagePage; + return showOverlay + ? FullscreenTopOverlay( + entries: entries, + index: _currentHorizontalPage, + scale: _topOverlayScale, + canToggleFavourite: hasCollection, + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + onActionSelected: (action) => _actionDelegate.onActionSelected(context, _entry, action), + ) + : const SizedBox.shrink(); + }, + ); + + final videoController = _entry != null && _entry.isVideo ? _videoControllers.firstWhere((kv) => kv.item1 == _entry.uri, orElse: () => null)?.item2 : null; + Widget bottomOverlay = Column( + children: [ + if (videoController != null) + VideoControlOverlay( + entry: _entry, + controller: videoController, + scale: _bottomOverlayScale, + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + ), + SlideTransition( + position: _bottomOverlayOffset, + child: FullscreenBottomOverlay( + entries: entries, + index: _currentHorizontalPage, + showPosition: hasCollection, + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + ), + ), + ], + ); + bottomOverlay = ValueListenableBuilder( + valueListenable: _overlayAnimationController, + builder: (context, animation, child) { + return Visibility( + visible: _entry != null && _overlayAnimationController.status != AnimationStatus.dismissed, + child: child, + ); + }, + child: bottomOverlay, + ); + + bottomOverlay = Selector( + selector: (c, mq) => mq.size.height, + builder: (c, mqHeight, child) { + // when orientation change, the `PageController` offset is not updated right away + // and it does not trigger its listeners when it does, so we force a refresh in the next frame + WidgetsBinding.instance.addPostFrameCallback((_) => _onVerticalPageControllerChange()); + return AnimatedBuilder( + animation: _verticalScrollNotifier, + builder: (context, child) => Positioned( + bottom: (_verticalPager.offset ?? 0) - mqHeight, + child: child, + ), + child: child, + ); + }, + child: bottomOverlay, + ); + return WillPopScope( onWillPop: () { if (_currentVerticalPage.value == infoPage) { @@ -147,63 +220,17 @@ class FullscreenBodyState extends State with SingleTickerProvide onImageTap: () => _overlayVisible.value = !_overlayVisible.value, onImagePageRequested: () => _goToVerticalPage(imagePage), ), - ValueListenableBuilder( - valueListenable: _currentVerticalPage, - builder: (context, page, child) { - final showOverlay = _entry != null && page == imagePage; - return showOverlay - ? FullscreenTopOverlay( - entries: entries, - index: _currentHorizontalPage, - scale: _topOverlayScale, - canToggleFavourite: hasCollection, - viewInsets: _frozenViewInsets, - viewPadding: _frozenViewPadding, - onActionSelected: (action) => _actionDelegate.onActionSelected(context, _entry, action), - ) - : const SizedBox.shrink(); - }, - ), - ValueListenableBuilder( - valueListenable: _currentVerticalPage, - builder: (context, page, child) { - final showOverlay = _entry != null && page == imagePage; - final videoController = showOverlay && _entry.isVideo ? _videoControllers.firstWhere((kv) => kv.item1 == _entry.uri, orElse: () => null)?.item2 : null; - return Positioned( - bottom: 0, - child: Opacity( - opacity: showOverlay ? 1 : 0, - child: Column( - children: [ - if (videoController != null) - VideoControlOverlay( - entry: _entry, - controller: videoController, - scale: _bottomOverlayScale, - viewInsets: _frozenViewInsets, - viewPadding: _frozenViewPadding, - ), - SlideTransition( - position: _bottomOverlayOffset, - child: FullscreenBottomOverlay( - entries: entries, - index: _currentHorizontalPage, - showPosition: hasCollection, - viewInsets: _frozenViewInsets, - viewPadding: _frozenViewPadding, - ), - ), - ], - ), - ), - ); - }, - ), + topOverlay, + bottomOverlay, ], ), ); } + void _onVerticalPageControllerChange() { + _verticalScrollNotifier.notifyListeners(); + } + Future _goToVerticalPage(int page) { return _verticalPager.animateToPage( page,