From 272916eaa6961cce88ac64ee20a64c82989b67be Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 22 Nov 2020 22:57:11 +0900 Subject: [PATCH] album pick page layout fixes --- lib/utils/durations.dart | 2 +- .../selection_action_delegate.dart | 40 +----- lib/widgets/filter_grids/album_pick.dart | 116 ++++++++++++++---- .../filter_grids/common/filter_grid_page.dart | 14 ++- 4 files changed, 110 insertions(+), 62 deletions(-) diff --git a/lib/utils/durations.dart b/lib/utils/durations.dart index af41013c9..4c794da88 100644 --- a/lib/utils/durations.dart +++ b/lib/utils/durations.dart @@ -42,5 +42,5 @@ class Durations { static Duration staggeredAnimationDelay = Durations.staggeredAnimation ~/ 6 * timeDilation; static const doubleBackTimerDelay = Duration(milliseconds: 1000); static const softKeyboardDisplayDelay = Duration(milliseconds: 300); - static const searchDebounceDelay = Duration(milliseconds: 200); + static const searchDebounceDelay = Duration(milliseconds: 250); } diff --git a/lib/widgets/common/action_delegates/selection_action_delegate.dart b/lib/widgets/common/action_delegates/selection_action_delegate.dart index 698cd5178..4c15a613b 100644 --- a/lib/widgets/common/action_delegates/selection_action_delegate.dart +++ b/lib/widgets/common/action_delegates/selection_action_delegate.dart @@ -1,30 +1,21 @@ import 'dart:async'; -import 'package:aves/model/filters/album.dart'; import 'package:aves/model/image_entry.dart'; -import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/image_file_service.dart'; import 'package:aves/widgets/collection/collection_actions.dart'; -import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/action_delegates/feedback.dart'; import 'package:aves/widgets/common/action_delegates/permission_aware.dart'; import 'package:aves/widgets/common/action_delegates/size_aware.dart'; import 'package:aves/widgets/common/aves_dialog.dart'; import 'package:aves/widgets/common/entry_actions.dart'; -import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/filter_grids/album_pick.dart'; -import 'package:aves/widgets/filter_grids/albums_page.dart'; -import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart'; -import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:intl/intl.dart'; -import 'package:provider/provider.dart'; class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { final CollectionLens collection; @@ -71,38 +62,11 @@ class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwar } Future _moveSelection(BuildContext context, {@required bool copy}) async { - final filterNotifier = ValueNotifier(''); - final chipSetActionDelegate = AlbumChipSetActionDelegate(source: source); final destinationAlbum = await Navigator.push( context, MaterialPageRoute( - builder: (context) { - Widget appBar = AlbumPickAppBar( - copy: copy, - actionDelegate: chipSetActionDelegate, - onFilterChanged: (filter) => filterNotifier.value = filter, - ); - - return Selector( - selector: (context, s) => s.albumSortFactor, - builder: (context, sortFactor, child) { - return ValueListenableBuilder( - valueListenable: filterNotifier, - builder: (context, filter, child) => FilterGridPage( - source: source, - appBar: appBar, - filterEntries: AlbumListPage.getAlbumEntries(source, filter: filter), - filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)), - emptyBuilder: () => EmptyContent( - icon: AIcons.album, - text: 'No albums', - ), - onTap: (filter) => Navigator.pop(context, (filter as AlbumFilter)?.album), - ), - ); - }, - ); - }, + settings: RouteSettings(name: AlbumPickPage.routeName), + builder: (context) => AlbumPickPage(source: source, copy: copy), ), ); if (destinationAlbum == null || destinationAlbum.isEmpty) return; diff --git a/lib/widgets/filter_grids/album_pick.dart b/lib/widgets/filter_grids/album_pick.dart index 81b9581fe..09c653e15 100644 --- a/lib/widgets/filter_grids/album_pick.dart +++ b/lib/widgets/filter_grids/album_pick.dart @@ -1,22 +1,83 @@ +import 'package:aves/model/filters/album.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/enums.dart'; import 'package:aves/utils/debouncer.dart'; import 'package:aves/utils/durations.dart'; +import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/action_delegates/create_album_dialog.dart'; import 'package:aves/widgets/common/icons.dart'; +import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/common/chip_actions.dart'; import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart'; +import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +class AlbumPickPage extends StatefulWidget { + static const routeName = '/album_pick'; + + final CollectionSource source; + final bool copy; + + const AlbumPickPage({ + @required this.source, + @required this.copy, + }); + + @override + _AlbumPickPageState createState() => _AlbumPickPageState(); +} + +class _AlbumPickPageState extends State { + final _filterNotifier = ValueNotifier(''); + + CollectionSource get source => widget.source; + + @override + Widget build(BuildContext context) { + Widget appBar = AlbumPickAppBar( + copy: widget.copy, + actionDelegate: AlbumChipSetActionDelegate(source: source), + filterNotifier: _filterNotifier, + ); + + return Selector( + selector: (context, s) => s.albumSortFactor, + builder: (context, sortFactor, child) { + return ValueListenableBuilder( + valueListenable: _filterNotifier, + builder: (context, filter, child) => FilterGridPage( + source: source, + appBar: appBar, + filterEntries: AlbumListPage.getAlbumEntries(source, filter: filter), + filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)), + emptyBuilder: () => EmptyContent( + icon: AIcons.album, + text: 'No albums', + ), + appBarHeight: AlbumPickAppBar.preferredHeight, + onTap: (filter) => Navigator.pop(context, (filter as AlbumFilter)?.album), + ), + ); + }, + ); + } +} class AlbumPickAppBar extends StatelessWidget { final bool copy; final AlbumChipSetActionDelegate actionDelegate; - final ValueChanged onFilterChanged; + final ValueNotifier filterNotifier; + + static const preferredHeight = kToolbarHeight + AlbumFilterBar.preferredHeight; const AlbumPickAppBar({ @required this.copy, @required this.actionDelegate, - @required this.onFilterChanged, + @required this.filterNotifier, }); @override @@ -25,7 +86,7 @@ class AlbumPickAppBar extends StatelessWidget { leading: BackButton(), title: Text(copy ? 'Copy to Album' : 'Move to Album'), bottom: AlbumFilterBar( - onChanged: onFilterChanged, + filterNotifier: filterNotifier, ), actions: [ IconButton( @@ -53,20 +114,30 @@ class AlbumPickAppBar extends StatelessWidget { } class AlbumFilterBar extends StatefulWidget implements PreferredSizeWidget { - final ValueChanged onChanged; + final ValueNotifier filterNotifier; - const AlbumFilterBar({@required this.onChanged}); + static const preferredHeight = kToolbarHeight; + + const AlbumFilterBar({@required this.filterNotifier}); @override - Size get preferredSize => Size.fromHeight(kToolbarHeight); + Size get preferredSize => Size.fromHeight(preferredHeight); @override _AlbumFilterBarState createState() => _AlbumFilterBarState(); } class _AlbumFilterBarState extends State { - final TextEditingController _controller = TextEditingController(text: ''); final Debouncer _debouncer = Debouncer(delay: Durations.searchDebounceDelay); + TextEditingController _controller; + + ValueNotifier get filterNotifier => widget.filterNotifier; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(text: filterNotifier.value); + } @override Widget build(BuildContext context) { @@ -74,12 +145,12 @@ class _AlbumFilterBarState extends State { icon: Icon(AIcons.clear), onPressed: () { _controller.clear(); - widget.onChanged(''); + filterNotifier.value = ''; }, tooltip: 'Clear', ); return Container( - height: kToolbarHeight, + height: AlbumFilterBar.preferredHeight, alignment: Alignment.topCenter, child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -98,22 +169,25 @@ class _AlbumFilterBarState extends State { hintStyle: Theme.of(context).inputDecorationTheme.hintStyle, ), textInputAction: TextInputAction.search, - onChanged: (s) => _debouncer(() => widget.onChanged(s)), + onChanged: (s) => _debouncer(() => filterNotifier.value = s), ), ), - AnimatedBuilder( - animation: _controller, - builder: (context, child) => AnimatedSwitcher( - duration: Durations.appBarActionChangeAnimation, - transitionBuilder: (child, animation) => FadeTransition( - opacity: animation, - child: SizeTransition( - axis: Axis.horizontal, - sizeFactor: animation, - child: child, + ConstrainedBox( + constraints: BoxConstraints(minWidth: 16), + child: AnimatedBuilder( + animation: _controller, + builder: (context, child) => AnimatedSwitcher( + duration: Durations.appBarActionChangeAnimation, + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + axis: Axis.horizontal, + sizeFactor: animation, + child: child, + ), ), + child: _controller.text.isNotEmpty ? clearButton : SizedBox.shrink(), ), - child: _controller.text.isNotEmpty ? clearButton : SizedBox(width: 16), ), ) ], diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 04a3fd5c0..012e8ad40 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -170,6 +170,7 @@ class FilterGridPage extends StatelessWidget { final Map filterEntries; final CollectionFilter Function(String key) filterBuilder; final Widget Function() emptyBuilder; + final double appBarHeight; final FilterCallback onTap; final OffsetFilterCallback onLongPress; @@ -179,6 +180,7 @@ class FilterGridPage extends StatelessWidget { @required this.filterEntries, @required this.filterBuilder, @required this.emptyBuilder, + this.appBarHeight = kToolbarHeight, @required this.onTap, this.onLongPress, }); @@ -227,7 +229,7 @@ class FilterGridPage extends StatelessWidget { controller: PrimaryScrollController.of(context), padding: EdgeInsets.only( // padding to keep scroll thumb between app bar above and nav bar below - top: kToolbarHeight, + top: appBarHeight, bottom: mqViewInsetsBottom, ), child: scrollView, @@ -243,7 +245,15 @@ class FilterGridPage extends StatelessWidget { appBar, filterKeys.isEmpty ? SliverFillRemaining( - child: emptyBuilder(), + child: Selector( + selector: (context, mq) => mq.viewInsets.bottom, + builder: (context, mqViewInsetsBottom, child) { + return Padding( + padding: EdgeInsets.only(bottom: mqViewInsetsBottom), + child: emptyBuilder(), + ); + }, + ), hasScrollBody: false, ) : SliverPadding(