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> 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 {}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
Loading…
Reference in a new issue