From 02d869c02a1701a5ffb2c7e59a0677e0b8c56167 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Fri, 5 Jun 2020 14:45:38 +0900 Subject: [PATCH] app bar: loading feedback --- lib/model/collection_source.dart | 3 + lib/widgets/album/app_bar.dart | 65 ++++++++++++++++++- .../media_store_collection_provider.dart | 4 ++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/lib/model/collection_source.dart b/lib/model/collection_source.dart index 206fb9381..52b45ec30 100644 --- a/lib/model/collection_source.dart +++ b/lib/model/collection_source.dart @@ -19,6 +19,7 @@ class CollectionSource { List sortedCountries = List.unmodifiable([]); List sortedPlaces = List.unmodifiable([]); List sortedTags = List.unmodifiable([]); + ValueNotifier stateNotifier = ValueNotifier(SourceState.ready); List get entries => List.unmodifiable(_rawEntries); @@ -249,6 +250,8 @@ class CollectionSource { } } +enum SourceState { loading, cataloging, locating, ready } + class AddressMetadataChangedEvent {} class CatalogMetadataChangedEvent {} diff --git a/lib/widgets/album/app_bar.dart b/lib/widgets/album/app_bar.dart index 03b1c2fa9..4cd36a29e 100644 --- a/lib/widgets/album/app_bar.dart +++ b/lib/widgets/album/app_bar.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:aves/main.dart'; import 'package:aves/model/collection_lens.dart'; +import 'package:aves/model/collection_source.dart'; import 'package:aves/model/settings.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/album/filter_bar.dart'; @@ -13,6 +14,7 @@ import 'package:aves/widgets/common/menu_row.dart'; import 'package:aves/widgets/stats/stats.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:intl/intl.dart'; import 'package:pedantic/pedantic.dart'; @@ -123,7 +125,66 @@ class _CollectionAppBarState extends State with SingleTickerPr Widget _buildAppBarTitle() { if (collection.isBrowsing) { - final title = AvesApp.mode == AppMode.pick ? 'Select' : 'Aves'; + Widget title = Text(AvesApp.mode == AppMode.pick ? 'Select' : 'Aves'); + if (AvesApp.mode == AppMode.main) { + title = Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title, + ValueListenableBuilder( + valueListenable: collection.source.stateNotifier, + builder: (context, sourceState, child) { + String subtitle; + switch (sourceState) { + case SourceState.loading: + subtitle = 'Loading'; + break; + case SourceState.cataloging: + subtitle = 'Cataloging'; + break; + case SourceState.locating: + subtitle = 'Locating'; + break; + case SourceState.ready: + default: + break; + } + final subtitleStyle = Theme.of(context).textTheme.caption; + final progressIndicatorSize = subtitleStyle.fontSize; + return AnimatedSwitcher( + duration: Duration(milliseconds: (300 * timeDilation).toInt()), + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + child: child, + sizeFactor: animation, + ), + ), + child: subtitle == null + ? const SizedBox.shrink() + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(1), + width: progressIndicatorSize, + height: progressIndicatorSize, + margin: const EdgeInsetsDirectional.only(end: 8), + child: const CircularProgressIndicator(strokeWidth: 1), + ), + Text( + '$subtitle', + style: subtitleStyle, + ), + ], + ), + ); + }, + ), + ], + ); + } return GestureDetector( onTap: _goToSearch, // use a `Container` with a dummy color to make it expand @@ -133,7 +194,7 @@ class _CollectionAppBarState extends State with SingleTickerPr padding: const EdgeInsets.symmetric(horizontal: NavigationToolbar.kMiddleSpacing), color: Colors.transparent, height: kToolbarHeight, - child: Text(title), + child: title, ), ); } else if (collection.isSelecting) { diff --git a/lib/widgets/common/data_providers/media_store_collection_provider.dart b/lib/widgets/common/data_providers/media_store_collection_provider.dart index 4dd39f4f0..c90e1c119 100644 --- a/lib/widgets/common/data_providers/media_store_collection_provider.dart +++ b/lib/widgets/common/data_providers/media_store_collection_provider.dart @@ -17,6 +17,7 @@ class MediaStoreSource { Future fetch() async { final stopwatch = Stopwatch()..start(); + source.stateNotifier.value = SourceState.loading; await metadataDb.init(); // <20ms await favourites.init(); final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms @@ -48,10 +49,13 @@ class MediaStoreSource { source.addAll(allEntries); // TODO reduce setup time until here source.updateAlbums(); // <50ms + source.stateNotifier.value = SourceState.cataloging; await source.loadCatalogMetadata(); // 400ms for 5400 entries await source.catalogEntries(); // <50ms + source.stateNotifier.value = SourceState.locating; await source.loadAddresses(); // 350ms await source.locateEntries(); // <50ms + source.stateNotifier.value = SourceState.ready; debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}'); }, onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),