diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index c946af499..37140c22a 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -164,6 +164,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM return _filterEntryCountMap.putIfAbsent(filter, () => _rawEntries.where((entry) => filter.filter(entry)).length); } + bool get initialized => false; + Future init(); Future refresh(); diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart index 6f93289e1..f21ab63ab 100644 --- a/lib/model/source/media_store_source.dart +++ b/lib/model/source/media_store_source.dart @@ -13,6 +13,11 @@ import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:pedantic/pedantic.dart'; class MediaStoreSource extends CollectionSource { + bool _initialized = false; + + @override + bool get initialized => _initialized; + @override Future init() async { final stopwatch = Stopwatch()..start(); @@ -29,6 +34,7 @@ class MediaStoreSource extends CollectionSource { settings.catalogTimeZone = currentTimeZone; } await loadDates(); // 100ms for 5400 entries + _initialized = true; debugPrint('$runtimeType init done, elapsed=${stopwatch.elapsed}'); } diff --git a/lib/widgets/filter_grids/album_pick.dart b/lib/widgets/filter_grids/album_pick.dart index a71522cde..edb98101d 100644 --- a/lib/widgets/filter_grids/album_pick.dart +++ b/lib/widgets/filter_grids/album_pick.dart @@ -2,10 +2,14 @@ import 'package:aves/model/actions/chip_actions.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; +import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/collection/empty.dart'; +import 'package:aves/widgets/common/app_bar_subtitle.dart'; +import 'package:aves/widgets/common/basic/menu_row.dart'; import 'package:aves/widgets/common/basic/query_bar.dart'; import 'package:aves/widgets/dialogs/create_album_dialog.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; @@ -13,8 +17,10 @@ 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/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; class AlbumPickPage extends StatefulWidget { static const routeName = '/album_pick'; @@ -39,32 +45,36 @@ class _AlbumPickPageState extends State { @override Widget build(BuildContext context) { Widget appBar = AlbumPickAppBar( + source: source, moveType: widget.moveType, actionDelegate: AlbumChipSetActionDelegate(source: source), queryNotifier: _queryNotifier, ); - return Selector( - selector: (context, s) => s.albumSortFactor, - builder: (context, sortFactor, child) { - return FilterGridPage( - source: source, - appBar: appBar, - filterSections: AlbumListPage.getAlbumEntries(source), - showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none, - applyQuery: (filters, query) { - if (query == null || query.isEmpty) return filters; - query = query.toUpperCase(); - return filters.where((item) => item.filter.uniqueName.toUpperCase().contains(query)).toList(); - }, - queryNotifier: _queryNotifier, - emptyBuilder: () => EmptyContent( - icon: AIcons.album, - text: 'No albums', + return Selector>( + selector: (context, s) => Tuple2(s.albumGroupFactor, s.albumSortFactor), + builder: (context, s, child) { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) => FilterGridPage( + source: source, + appBar: appBar, + filterSections: AlbumListPage.getAlbumEntries(source), + showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none, + applyQuery: (filters, query) { + if (query == null || query.isEmpty) return filters; + query = query.toUpperCase(); + return filters.where((item) => item.filter.uniqueName.toUpperCase().contains(query)).toList(); + }, + queryNotifier: _queryNotifier, + emptyBuilder: () => EmptyContent( + icon: AIcons.album, + text: 'No albums', + ), + settingsRouteKey: AlbumListPage.routeName, + appBarHeight: AlbumPickAppBar.preferredHeight, + onTap: (filter) => Navigator.pop(context, (filter as AlbumFilter)?.album), ), - settingsRouteKey: AlbumListPage.routeName, - appBarHeight: AlbumPickAppBar.preferredHeight, - onTap: (filter) => Navigator.pop(context, (filter as AlbumFilter)?.album), ); }, ); @@ -72,6 +82,7 @@ class _AlbumPickPageState extends State { } class AlbumPickAppBar extends StatelessWidget { + final CollectionSource source; final MoveType moveType; final AlbumChipSetActionDelegate actionDelegate; final ValueNotifier queryNotifier; @@ -79,6 +90,7 @@ class AlbumPickAppBar extends StatelessWidget { static const preferredHeight = kToolbarHeight + AlbumFilterBar.preferredHeight; const AlbumPickAppBar({ + @required this.source, @required this.moveType, @required this.actionDelegate, @required this.queryNotifier, @@ -101,7 +113,10 @@ class AlbumPickAppBar extends StatelessWidget { return SliverAppBar( leading: BackButton(), - title: Text(title()), + title: SourceStateAwareAppBarTitle( + title: Text(title()), + source: source, + ), bottom: AlbumFilterBar( filterNotifier: queryNotifier, ), @@ -119,10 +134,23 @@ class AlbumPickAppBar extends StatelessWidget { }, tooltip: 'Create album', ), - IconButton( - icon: Icon(AIcons.sort), - onPressed: () => actionDelegate.onActionSelected(context, ChipSetAction.sort), - tooltip: 'Sort…', + PopupMenuButton( + itemBuilder: (context) { + return [ + PopupMenuItem( + value: ChipSetAction.sort, + child: MenuRow(text: 'Sort…', icon: AIcons.sort), + ), + PopupMenuItem( + value: ChipSetAction.group, + child: MenuRow(text: 'Group…', icon: AIcons.group), + ), + ]; + }, + onSelected: (action) { + // wait for the popup menu to hide before proceeding with the action + Future.delayed(Durations.popupMenuAnimation * timeDilation, () => actionDelegate.onActionSelected(context, action)); + }, ), ], floating: true, diff --git a/lib/widgets/filter_grids/common/chip_set_action_delegate.dart b/lib/widgets/filter_grids/common/chip_set_action_delegate.dart index 08e9e32d0..2625b42cb 100644 --- a/lib/widgets/filter_grids/common/chip_set_action_delegate.dart +++ b/lib/widgets/filter_grids/common/chip_set_action_delegate.dart @@ -92,7 +92,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate { builder: (context) => AvesSelectionDialog( initialValue: settings.albumGroupFactor, options: { - AlbumChipGroupFactor.importance: 'By importance', + AlbumChipGroupFactor.importance: 'By tier', AlbumChipGroupFactor.volume: 'By storage volume', AlbumChipGroupFactor.none: 'Do not group', }, diff --git a/lib/widgets/viewer/entry_action_delegate.dart b/lib/widgets/viewer/entry_action_delegate.dart index d46f09280..d81211a9e 100644 --- a/lib/widgets/viewer/entry_action_delegate.dart +++ b/lib/widgets/viewer/entry_action_delegate.dart @@ -153,6 +153,10 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix Future _showExportDialog(BuildContext context, AvesEntry entry) async { final source = context.read(); + if (!source.initialized) { + await source.init(); + unawaited(source.refresh()); + } final destinationAlbum = await Navigator.push( context, MaterialPageRoute(