app bar: loading feedback
This commit is contained in:
parent
40a31a667d
commit
02d869c02a
3 changed files with 70 additions and 2 deletions
|
@ -19,6 +19,7 @@ class CollectionSource {
|
|||
List<String> sortedCountries = List.unmodifiable([]);
|
||||
List<String> sortedPlaces = List.unmodifiable([]);
|
||||
List<String> sortedTags = List.unmodifiable([]);
|
||||
ValueNotifier<SourceState> stateNotifier = ValueNotifier<SourceState>(SourceState.ready);
|
||||
|
||||
List<ImageEntry> get entries => List.unmodifiable(_rawEntries);
|
||||
|
||||
|
@ -249,6 +250,8 @@ class CollectionSource {
|
|||
}
|
||||
}
|
||||
|
||||
enum SourceState { loading, cataloging, locating, ready }
|
||||
|
||||
class AddressMetadataChangedEvent {}
|
||||
|
||||
class CatalogMetadataChangedEvent {}
|
||||
|
|
|
@ -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<CollectionAppBar> 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<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(
|
||||
onTap: _goToSearch,
|
||||
// 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),
|
||||
color: Colors.transparent,
|
||||
height: kToolbarHeight,
|
||||
child: Text(title),
|
||||
child: title,
|
||||
),
|
||||
);
|
||||
} else if (collection.isSelecting) {
|
||||
|
|
|
@ -17,6 +17,7 @@ class MediaStoreSource {
|
|||
|
||||
Future<void> 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'),
|
||||
|
|
Loading…
Reference in a new issue