From b3fde095e9718264521380d8497e2c9b32b2f858 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 31 Mar 2020 10:44:39 +0900 Subject: [PATCH] collection: fixed scroll thumb top padding according to app bar height --- lib/utils/constants.dart | 3 + lib/widgets/album/collection_app_bar.dart | 134 ++++++++++-------- lib/widgets/album/collection_page.dart | 42 ++---- lib/widgets/album/collection_scaling.dart | 2 +- lib/widgets/album/thumbnail_collection.dart | 58 ++++---- lib/widgets/fullscreen/fullscreen_body.dart | 2 +- .../fullscreen/info/basic_section.dart | 2 +- .../fullscreen/info/location_section.dart | 8 +- lib/widgets/fullscreen/overlay/top.dart | 2 +- 9 files changed, 128 insertions(+), 125 deletions(-) diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index ebe8eecdb..29bea99f4 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -19,6 +19,9 @@ class Constants { ], ); + // ref _PopupMenuRoute._kMenuDuration + static const popupMenuTransitionDuration = Duration(milliseconds: 300); + // TODO TLAD smarter sizing, but shouldn't only depend on `extent` so that it doesn't reload during gridview scaling static const double thumbnailCacheExtent = 50; diff --git a/lib/widgets/album/collection_app_bar.dart b/lib/widgets/album/collection_app_bar.dart index 7b59b58cf..3007efbe0 100644 --- a/lib/widgets/album/collection_app_bar.dart +++ b/lib/widgets/album/collection_app_bar.dart @@ -1,6 +1,7 @@ import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/settings.dart'; +import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/album/filter_bar.dart'; import 'package:aves/widgets/album/search/search_delegate.dart'; @@ -12,13 +13,17 @@ import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:pedantic/pedantic.dart'; import 'package:provider/provider.dart'; -class CollectionAppBar extends StatefulWidget implements PreferredSizeWidget { +class CollectionAppBar extends StatefulWidget { final ValueNotifier stateNotifier; + final ValueNotifier appBarHeightNotifier; + final CollectionLens collection; - @override - final Size preferredSize = Size.fromHeight(kToolbarHeight + FilterBar.preferredHeight); - - CollectionAppBar({this.stateNotifier}); + const CollectionAppBar({ + Key key, + @required this.stateNotifier, + @required this.appBarHeightNotifier, + @required this.collection, + }) : super(key: key); @override _CollectionAppBarState createState() => _CollectionAppBarState(); @@ -31,6 +36,10 @@ class _CollectionAppBarState extends State with SingleTickerPr ValueNotifier get stateNotifier => widget.stateNotifier; + CollectionLens get collection => widget.collection; + + bool get hasFilters => collection.filters.isNotEmpty; + @override void initState() { super.initState(); @@ -39,6 +48,7 @@ class _CollectionAppBarState extends State with SingleTickerPr vsync: this, ); _registerWidget(widget); + WidgetsBinding.instance.addPostFrameCallback((_) => _updateHeight()); } @override @@ -56,11 +66,13 @@ class _CollectionAppBarState extends State with SingleTickerPr } void _registerWidget(CollectionAppBar widget) { - stateNotifier.addListener(_onStateChange); + widget.stateNotifier.addListener(_onStateChange); + widget.collection.filterChangeNotifier.addListener(_updateHeight); } void _unregisterWidget(CollectionAppBar widget) { - stateNotifier.removeListener(_onStateChange); + widget.stateNotifier.removeListener(_onStateChange); + widget.collection.filterChangeNotifier.removeListener(_updateHeight); } @override @@ -69,16 +81,14 @@ class _CollectionAppBarState extends State with SingleTickerPr valueListenable: stateNotifier, builder: (context, state, child) { debugPrint('$runtimeType builder state=$state'); - return Consumer( - builder: (context, collection, child) => AnimatedBuilder( - animation: collection.filterChangeNotifier, - builder: (context, child) => SliverAppBar( - leading: _buildAppBarLeading(), - title: _buildAppBarTitle(), - actions: _buildActions(), - bottom: collection.filters.isNotEmpty ? FilterBar() : null, - floating: true, - ), + return AnimatedBuilder( + animation: collection.filterChangeNotifier, + builder: (context, child) => SliverAppBar( + leading: _buildAppBarLeading(), + title: _buildAppBarTitle(), + actions: _buildActions(), + bottom: hasFilters ? FilterBar() : null, + floating: true, ), ); }, @@ -127,17 +137,15 @@ class _CollectionAppBarState extends State with SingleTickerPr builder: (context) { switch (stateNotifier.value) { case PageState.browse: - return Consumer( - builder: (context, collection, child) => IconButton( - icon: Icon(OMIcons.search), - onPressed: () async { - final filter = await showSearch( - context: context, - delegate: ImageSearchDelegate(collection), - ); - collection.addFilter(filter); - }, - ), + return IconButton( + icon: Icon(OMIcons.search), + onPressed: () async { + final filter = await showSearch( + context: context, + delegate: ImageSearchDelegate(collection), + ); + collection.addFilter(filter); + }, ); case PageState.search: return IconButton( @@ -149,55 +157,53 @@ class _CollectionAppBarState extends State with SingleTickerPr }, ), Builder( - builder: (context) => Consumer( - builder: (context, collection, child) => PopupMenuButton( - itemBuilder: (context) => [ + builder: (context) => PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + value: CollectionAction.sortByDate, + child: MenuRow(text: 'Sort by date', checked: collection.sortFactor == SortFactor.date), + ), + PopupMenuItem( + value: CollectionAction.sortBySize, + child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size), + ), + PopupMenuItem( + value: CollectionAction.sortByName, + child: MenuRow(text: 'Sort by name', checked: collection.sortFactor == SortFactor.name), + ), + const PopupMenuDivider(), + if (collection.sortFactor == SortFactor.date) ...[ PopupMenuItem( - value: CollectionAction.sortByDate, - child: MenuRow(text: 'Sort by date', checked: collection.sortFactor == SortFactor.date), + value: CollectionAction.groupByAlbum, + child: MenuRow(text: 'Group by album', checked: collection.groupFactor == GroupFactor.album), ), PopupMenuItem( - value: CollectionAction.sortBySize, - child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size), + value: CollectionAction.groupByMonth, + child: MenuRow(text: 'Group by month', checked: collection.groupFactor == GroupFactor.month), ), PopupMenuItem( - value: CollectionAction.sortByName, - child: MenuRow(text: 'Sort by name', checked: collection.sortFactor == SortFactor.name), + value: CollectionAction.groupByDay, + child: MenuRow(text: 'Group by day', checked: collection.groupFactor == GroupFactor.day), ), const PopupMenuDivider(), - if (collection.sortFactor == SortFactor.date) ...[ - PopupMenuItem( - value: CollectionAction.groupByAlbum, - child: MenuRow(text: 'Group by album', checked: collection.groupFactor == GroupFactor.album), - ), - PopupMenuItem( - value: CollectionAction.groupByMonth, - child: MenuRow(text: 'Group by month', checked: collection.groupFactor == GroupFactor.month), - ), - PopupMenuItem( - value: CollectionAction.groupByDay, - child: MenuRow(text: 'Group by day', checked: collection.groupFactor == GroupFactor.day), - ), - const PopupMenuDivider(), - ], - PopupMenuItem( - value: CollectionAction.stats, - child: MenuRow(text: 'Stats', icon: OMIcons.pieChart), - ), ], - onSelected: (action) => _onActionSelected(collection, action), - ), + PopupMenuItem( + value: CollectionAction.stats, + child: MenuRow(text: 'Stats', icon: OMIcons.pieChart), + ), + ], + onSelected: _onActionSelected, ), ), ]; } - void _onActionSelected(CollectionLens collection, CollectionAction action) async { + void _onActionSelected(CollectionAction action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(const Duration(milliseconds: 300)); + await Future.delayed(Constants.popupMenuTransitionDuration); switch (action) { case CollectionAction.stats: - unawaited(_goToStats(collection)); + unawaited(_goToStats()); break; case CollectionAction.groupByAlbum: settings.collectionGroupFactor = GroupFactor.album; @@ -226,7 +232,7 @@ class _CollectionAppBarState extends State with SingleTickerPr } } - Future _goToStats(CollectionLens collection) { + Future _goToStats() { return Navigator.push( context, MaterialPageRoute( @@ -245,6 +251,10 @@ class _CollectionAppBarState extends State with SingleTickerPr _searchFieldController.clear(); } } + + void _updateHeight() { + widget.appBarHeightNotifier.value = kToolbarHeight + (hasFilters ? FilterBar.preferredHeight : 0); + } } class SearchField extends StatelessWidget { diff --git a/lib/widgets/album/collection_page.dart b/lib/widgets/album/collection_page.dart index 7782098ad..ed79581c4 100644 --- a/lib/widgets/album/collection_page.dart +++ b/lib/widgets/album/collection_page.dart @@ -1,7 +1,5 @@ import 'package:aves/model/collection_lens.dart'; -import 'package:aves/widgets/album/collection_app_bar.dart'; import 'package:aves/widgets/album/collection_drawer.dart'; -import 'package:aves/widgets/album/empty.dart'; import 'package:aves/widgets/album/thumbnail_collection.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:flutter/foundation.dart'; @@ -11,7 +9,9 @@ import 'package:provider/provider.dart'; class CollectionPage extends StatelessWidget { final CollectionLens collection; - const CollectionPage(this.collection); + final ValueNotifier _stateNotifier = ValueNotifier(PageState.browse); + + CollectionPage(this.collection); @override Widget build(BuildContext context) { @@ -20,7 +20,18 @@ class CollectionPage extends StatelessWidget { child: ChangeNotifierProvider.value( value: collection, child: Scaffold( - body: CollectionPageBody(), + body: WillPopScope( + onWillPop: () { + if (_stateNotifier.value == PageState.search) { + _stateNotifier.value = PageState.browse; + return SynchronousFuture(false); + } + return SynchronousFuture(true); + }, + child: ThumbnailCollection( + stateNotifier: _stateNotifier, + ), + ), drawer: CollectionDrawer( source: collection.source, ), @@ -31,27 +42,4 @@ class CollectionPage extends StatelessWidget { } } -class CollectionPageBody extends StatelessWidget { - final ValueNotifier _stateNotifier = ValueNotifier(PageState.browse); - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () { - if (_stateNotifier.value == PageState.search) { - _stateNotifier.value = PageState.browse; - return SynchronousFuture(false); - } - return SynchronousFuture(true); - }, - child: ThumbnailCollection( - appBar: CollectionAppBar( - stateNotifier: _stateNotifier, - ), - emptyBuilder: (context) => EmptyContent(), - ), - ); - } -} - enum PageState { browse, search } diff --git a/lib/widgets/album/collection_scaling.dart b/lib/widgets/album/collection_scaling.dart index 4becb6a1f..e33b91ce1 100644 --- a/lib/widgets/album/collection_scaling.dart +++ b/lib/widgets/album/collection_scaling.dart @@ -167,7 +167,7 @@ class _ScaleOverlayState extends State { ), ), duration: const Duration(milliseconds: 200), - child: ValueListenableBuilder( + child: ValueListenableBuilder( valueListenable: widget.scaledCountNotifier, builder: (context, columnCount, child) { final extent = gridWidth / columnCount; diff --git a/lib/widgets/album/thumbnail_collection.dart b/lib/widgets/album/thumbnail_collection.dart index fc6d4ccfc..79c039b16 100644 --- a/lib/widgets/album/thumbnail_collection.dart +++ b/lib/widgets/album/thumbnail_collection.dart @@ -1,22 +1,24 @@ import 'package:aves/model/collection_lens.dart'; +import 'package:aves/widgets/album/collection_app_bar.dart'; +import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/album/collection_scaling.dart'; import 'package:aves/widgets/album/collection_section.dart'; +import 'package:aves/widgets/album/empty.dart'; import 'package:aves/widgets/common/scroll_thumb.dart'; import 'package:draggable_scrollbar/draggable_scrollbar.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class ThumbnailCollection extends StatelessWidget { - final Widget appBar; - final WidgetBuilder emptyBuilder; + final ValueNotifier stateNotifier; + final ValueNotifier _appBarHeightNotifier = ValueNotifier(0); final ValueNotifier _columnCountNotifier = ValueNotifier(4); final GlobalKey _scrollableKey = GlobalKey(); ThumbnailCollection({ Key key, - this.appBar, - this.emptyBuilder, + @required this.stateNotifier, }) : super(key: key); @override @@ -27,16 +29,6 @@ class ThumbnailCollection extends StatelessWidget { final sectionKeys = sections.keys.toList(); final showHeaders = collection.showHeaders; - double topPadding = 0; - if (appBar != null) { - final topWidget = appBar; - if (topWidget is PreferredSizeWidget) { - topPadding = topWidget.preferredSize.height; - } else if (topWidget is SliverAppBar) { - topPadding = kToolbarHeight + (topWidget.bottom?.preferredSize?.height ?? 0.0); - } - } - return SafeArea( child: Selector( selector: (c, mq) => mq.viewInsets.bottom, @@ -44,7 +36,7 @@ class ThumbnailCollection extends StatelessWidget { return GridScaleGestureDetector( scrollableKey: _scrollableKey, columnCountNotifier: _columnCountNotifier, - child: ValueListenableBuilder( + child: ValueListenableBuilder( valueListenable: _columnCountNotifier, builder: (context, columnCount, child) { debugPrint('$runtimeType builder columnCount=$columnCount entries=${collection.entryCount}'); @@ -55,10 +47,14 @@ class ThumbnailCollection extends StatelessWidget { // when there is no content and we use `SliverFillRemaining` physics: collection.isEmpty ? const NeverScrollableScrollPhysics() : null, slivers: [ - if (appBar != null) appBar, - if (collection.isEmpty && emptyBuilder != null) + CollectionAppBar( + stateNotifier: stateNotifier, + appBarHeightNotifier: _appBarHeightNotifier, + collection: collection, + ), + if (collection.isEmpty) SliverFillRemaining( - child: emptyBuilder(context), + child: EmptyContent(), hasScrollBody: false, ), ...sectionKeys.map((sectionKey) => SectionSliver( @@ -78,16 +74,22 @@ class ThumbnailCollection extends StatelessWidget { ], ); - return DraggableScrollbar( - heightScrollThumb: avesScrollThumbHeight, - backgroundColor: Colors.white, - scrollThumbBuilder: avesScrollThumbBuilder(), - controller: PrimaryScrollController.of(context), - padding: EdgeInsets.only( - // padding to get scroll thumb below app bar, above nav bar - top: topPadding, - bottom: mqViewInsetsBottom, - ), + return ValueListenableBuilder( + valueListenable: _appBarHeightNotifier, + builder: (context, appBarHeight, child) { + return DraggableScrollbar( + heightScrollThumb: avesScrollThumbHeight, + backgroundColor: Colors.white, + scrollThumbBuilder: avesScrollThumbBuilder(), + controller: PrimaryScrollController.of(context), + padding: EdgeInsets.only( + // padding to keep scroll thumb between app bar above and nav bar below + top: appBarHeight, + bottom: mqViewInsetsBottom, + ), + child: child, + ); + }, child: scrollView, ); }, diff --git a/lib/widgets/fullscreen/fullscreen_body.dart b/lib/widgets/fullscreen/fullscreen_body.dart index 2f98a4915..c9a333f6a 100644 --- a/lib/widgets/fullscreen/fullscreen_body.dart +++ b/lib/widgets/fullscreen/fullscreen_body.dart @@ -395,7 +395,7 @@ class _FullscreenVerticalPageViewState extends State ), ), ]; - return ValueListenableBuilder( + return ValueListenableBuilder( valueListenable: _backgroundColorNotifier, builder: (context, backgroundColor, child) => Container( color: backgroundColor, diff --git a/lib/widgets/fullscreen/info/basic_section.dart b/lib/widgets/fullscreen/info/basic_section.dart index feabdecc8..95a80e84d 100644 --- a/lib/widgets/fullscreen/info/basic_section.dart +++ b/lib/widgets/fullscreen/info/basic_section.dart @@ -45,7 +45,7 @@ class BasicSection extends StatelessWidget { 'URI': entry.uri ?? '?', if (entry.path != null) 'Path': entry.path, }), - ValueListenableBuilder( + ValueListenableBuilder( valueListenable: entry.isFavouriteNotifier, builder: (context, isFavourite, child) { final album = entry.directory; diff --git a/lib/widgets/fullscreen/info/location_section.dart b/lib/widgets/fullscreen/info/location_section.dart index 58267ed41..751b0a508 100644 --- a/lib/widgets/fullscreen/info/location_section.dart +++ b/lib/widgets/fullscreen/info/location_section.dart @@ -57,14 +57,14 @@ class _LocationSectionState extends State { } void _registerWidget(LocationSection widget) { - entry.metadataChangeNotifier.addListener(_handleChange); - entry.addressChangeNotifier.addListener(_handleChange); + widget.entry.metadataChangeNotifier.addListener(_handleChange); + widget.entry.addressChangeNotifier.addListener(_handleChange); widget.visibleNotifier.addListener(_handleChange); } void _unregisterWidget(LocationSection widget) { - entry.metadataChangeNotifier.removeListener(_handleChange); - entry.addressChangeNotifier.removeListener(_handleChange); + widget.entry.metadataChangeNotifier.removeListener(_handleChange); + widget.entry.addressChangeNotifier.removeListener(_handleChange); widget.visibleNotifier.removeListener(_handleChange); } diff --git a/lib/widgets/fullscreen/overlay/top.dart b/lib/widgets/fullscreen/overlay/top.dart index b39831bb4..4dc5fe82e 100644 --- a/lib/widgets/fullscreen/overlay/top.dart +++ b/lib/widgets/fullscreen/overlay/top.dart @@ -41,7 +41,7 @@ class FullscreenTopOverlay extends StatelessWidget { const Spacer(), OverlayButton( scale: scale, - child: ValueListenableBuilder( + child: ValueListenableBuilder( valueListenable: entry.isFavouriteNotifier, builder: (context, isFavourite, child) => Stack( alignment: Alignment.center,