app bar: loading feedback

This commit is contained in:
Thibault Deckers 2020-06-05 14:45:38 +09:00
parent 40a31a667d
commit 02d869c02a
3 changed files with 70 additions and 2 deletions

View file

@ -19,6 +19,7 @@ class CollectionSource {
List<String> sortedCountries = List.unmodifiable([]); List<String> sortedCountries = List.unmodifiable([]);
List<String> sortedPlaces = List.unmodifiable([]); List<String> sortedPlaces = List.unmodifiable([]);
List<String> sortedTags = List.unmodifiable([]); List<String> sortedTags = List.unmodifiable([]);
ValueNotifier<SourceState> stateNotifier = ValueNotifier<SourceState>(SourceState.ready);
List<ImageEntry> get entries => List.unmodifiable(_rawEntries); List<ImageEntry> get entries => List.unmodifiable(_rawEntries);
@ -249,6 +250,8 @@ class CollectionSource {
} }
} }
enum SourceState { loading, cataloging, locating, ready }
class AddressMetadataChangedEvent {} class AddressMetadataChangedEvent {}
class CatalogMetadataChangedEvent {} class CatalogMetadataChangedEvent {}

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:aves/main.dart'; import 'package:aves/main.dart';
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/collection_source.dart';
import 'package:aves/model/settings.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/album/filter_bar.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:aves/widgets/stats/stats.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
@ -123,7 +125,66 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
Widget _buildAppBarTitle() { Widget _buildAppBarTitle() {
if (collection.isBrowsing) { 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<SourceState>(
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( return GestureDetector(
onTap: _goToSearch, onTap: _goToSearch,
// use a `Container` with a dummy color to make it expand // use a `Container` with a dummy color to make it expand
@ -133,7 +194,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
padding: const EdgeInsets.symmetric(horizontal: NavigationToolbar.kMiddleSpacing), padding: const EdgeInsets.symmetric(horizontal: NavigationToolbar.kMiddleSpacing),
color: Colors.transparent, color: Colors.transparent,
height: kToolbarHeight, height: kToolbarHeight,
child: Text(title), child: title,
), ),
); );
} else if (collection.isSelecting) { } else if (collection.isSelecting) {

View file

@ -17,6 +17,7 @@ class MediaStoreSource {
Future<void> fetch() async { Future<void> fetch() async {
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
source.stateNotifier.value = SourceState.loading;
await metadataDb.init(); // <20ms await metadataDb.init(); // <20ms
await favourites.init(); await favourites.init();
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
@ -48,10 +49,13 @@ class MediaStoreSource {
source.addAll(allEntries); source.addAll(allEntries);
// TODO reduce setup time until here // TODO reduce setup time until here
source.updateAlbums(); // <50ms source.updateAlbums(); // <50ms
source.stateNotifier.value = SourceState.cataloging;
await source.loadCatalogMetadata(); // 400ms for 5400 entries await source.loadCatalogMetadata(); // 400ms for 5400 entries
await source.catalogEntries(); // <50ms await source.catalogEntries(); // <50ms
source.stateNotifier.value = SourceState.locating;
await source.loadAddresses(); // 350ms await source.loadAddresses(); // 350ms
await source.locateEntries(); // <50ms await source.locateEntries(); // <50ms
source.stateNotifier.value = SourceState.ready;
debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}'); debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}');
}, },
onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'), onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),