media store collection provider

This commit is contained in:
Thibault Deckers 2019-12-26 17:37:56 +09:00
parent dc14c354a8
commit 582afba3e9
7 changed files with 131 additions and 69 deletions

View file

@ -1,7 +1,3 @@
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/image_file_service.dart';
import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/settings.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/album/all_collection_drawer.dart';
@ -9,17 +5,14 @@ import 'package:aves/widgets/album/all_collection_page.dart';
import 'package:aves/widgets/common/fake_app_bar.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/common/media_query_data_provider.dart';
import 'package:aves/widgets/common/media_store_collection_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:pedantic/pedantic.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:screen/screen.dart';
final _stopwatch = Stopwatch()..start();
void main() {
debugPrint('main start, elapsed=${_stopwatch.elapsed}');
runApp(AvesApp());
}
@ -42,31 +35,32 @@ class AvesApp extends StatelessWidget {
),
),
),
home: HomePage(),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage();
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
ImageCollection localMediaCollection = ImageCollection(entries: []);
Future<void> _appSetup;
@override
void initState() {
debugPrint('$runtimeType initState');
super.initState();
_appSetup = _setup();
imageCache.maximumSizeBytes = 512 * (1 << 20);
setup();
Screen.keepOn(true);
}
Future<void> setup() async {
debugPrint('$runtimeType setup start, elapsed=${_stopwatch.elapsed}');
Future<void> _setup() async {
debugPrint('$runtimeType _setup');
// TODO reduce permission check time
// TODO TLAD ask android.permission.ACCESS_MEDIA_LOCATION (unredacted EXIF with scoped storage)
final permissions = await PermissionHandler().requestPermissions([
@ -76,57 +70,41 @@ class _HomePageState extends State<HomePage> {
unawaited(SystemNavigator.pop());
return;
}
// debugPrint('$runtimeType setup permission check done, elapsed=${stopwatch.elapsed}');
androidFileUtils.init();
// debugPrint('$runtimeType setup androidFileUtils.init done, elapsed=${stopwatch.elapsed}');
// TODO notify when icons are ready for drawer and section header refresh
unawaited(IconUtils.init()); // 170ms
// debugPrint('$runtimeType setup IconUtils.init done, elapsed=${stopwatch.elapsed}');
await settings.init(); // <20ms
localMediaCollection.groupFactor = settings.collectionGroupFactor;
localMediaCollection.sortFactor = settings.collectionSortFactor;
debugPrint('$runtimeType setup settings.init done, elapsed=${_stopwatch.elapsed}');
await metadataDb.init(); // <20ms
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
final catalogTimeZone = settings.catalogTimeZone;
if (currentTimeZone != catalogTimeZone) {
// clear catalog metadata to get correct date/times when moving to a different time zone
debugPrint('$runtimeType clear catalog metadata to get correct date/times');
await metadataDb.clearMetadataEntries();
settings.catalogTimeZone = currentTimeZone;
}
// debugPrint('$runtimeType setup metadataDb.init done, elapsed=${stopwatch.elapsed}');
eventChannel.receiveBroadcastStream().cast<Map>().listen(
(entryMap) => localMediaCollection.add(ImageEntry.fromMap(entryMap)),
onDone: () async {
debugPrint('$runtimeType mediastore stream done, elapsed=${_stopwatch.elapsed}');
localMediaCollection.updateSections(); // <50ms
// TODO reduce setup time until here
localMediaCollection.updateAlbums(); // <50ms
await localMediaCollection.loadCatalogMetadata(); // 650ms
await localMediaCollection.catalogEntries(); // <50ms
await localMediaCollection.loadAddresses(); // 350ms
await localMediaCollection.locateEntries(); // <50ms
debugPrint('$runtimeType setup end, elapsed=${_stopwatch.elapsed}');
},
onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),
);
// debugPrint('$runtimeType setup fetch images, elapsed=${stopwatch.elapsed}');
// TODO split image fetch AND/OR cache fetch across sessions
await ImageFileService.getImageEntries(); // 460ms
}
@override
Widget build(BuildContext context) {
return MediaQueryDataProvider(
child: FutureBuilder(
future: _appSetup,
builder: (futureContext, AsyncSnapshot<void> snapshot) {
if (snapshot.hasError) return const Icon(Icons.error);
if (snapshot.connectionState != ConnectionState.done) return const CircularProgressIndicator();
debugPrint('$runtimeType FutureBuilder builder');
return const MediaStoreCollectionPage();
}),
);
}
}
class MediaStoreCollectionPage extends StatelessWidget {
const MediaStoreCollectionPage();
@override
Widget build(BuildContext context) {
debugPrint('$runtimeType build');
return MediaStoreCollectionProvider(
child: Scaffold(
// fake app bar so that content is safe from status bar, even though we use a SliverAppBar
appBar: FakeAppBar(),
body: AllCollectionPage(collection: localMediaCollection),
drawer: AllCollectionDrawer(collection: localMediaCollection),
body: const AllCollectionPage(),
drawer: const AllCollectionDrawer(),
resizeToAvoidBottomInset: false,
),
);

View file

@ -64,6 +64,7 @@ class ImageCollection with ChangeNotifier {
]);
break;
}
debugPrint('$runtimeType updateSections');
notifyListeners();
}

View file

@ -9,12 +9,11 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class AllCollectionDrawer extends StatelessWidget {
final ImageCollection collection;
const AllCollectionDrawer({Key key, this.collection}) : super(key: key);
const AllCollectionDrawer();
@override
Widget build(BuildContext context) {
final collection = Provider.of<ImageCollection>(context);
final albums = collection.sortedAlbums;
final tags = collection.sortedTags;
return Drawer(

View file

@ -5,14 +5,15 @@ import 'package:aves/widgets/album/thumbnail_collection.dart';
import 'package:aves/widgets/common/menu_row.dart';
import 'package:aves/widgets/debug_page.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AllCollectionPage extends StatelessWidget {
final ImageCollection collection;
const AllCollectionPage({Key key, this.collection}) : super(key: key);
const AllCollectionPage();
@override
Widget build(BuildContext context) {
debugPrint('$runtimeType build');
final collection = Provider.of<ImageCollection>(context);
return ThumbnailCollection(
collection: collection,
appBar: SliverAppBar(
@ -56,7 +57,7 @@ class AllCollectionPage extends StatelessWidget {
child: MenuRow(text: 'Debug', icon: Icons.whatshot),
),
],
onSelected: (action) => _onActionSelected(context, action),
onSelected: (action) => _onActionSelected(context, collection, action),
),
],
floating: true,
@ -64,10 +65,10 @@ class AllCollectionPage extends StatelessWidget {
);
}
void _onActionSelected(BuildContext context, AlbumAction action) {
void _onActionSelected(BuildContext context, ImageCollection collection, AlbumAction action) {
switch (action) {
case AlbumAction.debug:
goToDebug(context);
_goToDebug(context, collection);
break;
case AlbumAction.groupByAlbum:
settings.collectionGroupFactor = GroupFactor.album;
@ -92,7 +93,7 @@ class AllCollectionPage extends StatelessWidget {
}
}
Future goToDebug(BuildContext context) {
Future _goToDebug(BuildContext context, ImageCollection collection) {
return Navigator.push(
context,
MaterialPageRoute(

View file

@ -23,11 +23,14 @@ class ThumbnailCollection extends AnimatedWidget {
Widget build(BuildContext context) {
return Selector<MediaQueryData, double>(
selector: (c, mq) => mq.size.width,
builder: (c, mqWidth, child) => ThumbnailCollectionContent(
collection: collection,
appBar: appBar,
screenWidth: mqWidth,
),
builder: (c, mqWidth, child) {
debugPrint('$runtimeType builder mqWidth=$mqWidth');
return ThumbnailCollectionContent(
collection: collection,
appBar: appBar,
screenWidth: mqWidth,
);
},
);
}
}
@ -126,7 +129,9 @@ class SectionSliver extends StatelessWidget {
),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
// TODO TLAD find out why thumbnails are rebuilt when config change (show/hide status bar)
// TODO TLAD find out why thumbnails are rebuilt (with `initState`) when:
// - config change (show/hide status bar)
// - navigating away/back
(sliverContext, index) {
final sectionEntries = sections[sectionKey];
if (index >= sectionEntries.length) return null;
@ -161,6 +166,17 @@ class SectionSliver extends StatelessWidget {
),
),
);
// TODO TLAD consider the following to have transparency while popping fullscreen by drag down
// Navigator.push(
// context,
// PageRouteBuilder(
// opaque: false,
// pageBuilder: (BuildContext context, _, __) => FullscreenPage(
// collection: collection,
// initialUri: entry.uri,
// ),
// ),
// );
}
}

View file

@ -35,6 +35,7 @@ class ImagePreviewState extends State<ImagePreview> with AfterInitMixin {
@override
void initState() {
debugPrint('$runtimeType initState path=${entry.path}');
super.initState();
_entryChangeNotifier = Listenable.merge([
entry.imageChangeNotifier,

View file

@ -0,0 +1,66 @@
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/image_file_service.dart';
import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/settings.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:provider/provider.dart';
final _stopwatch = Stopwatch()..start();
class MediaStoreCollectionProvider extends StatelessWidget {
final Widget child;
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
const MediaStoreCollectionProvider({@required this.child});
Future<ImageCollection> _create() async {
debugPrint('$runtimeType _create, elapsed=${_stopwatch.elapsed}');
final mediaStoreCollection = ImageCollection(entries: []);
mediaStoreCollection.groupFactor = settings.collectionGroupFactor;
mediaStoreCollection.sortFactor = settings.collectionSortFactor;
await metadataDb.init(); // <20ms
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
final catalogTimeZone = settings.catalogTimeZone;
if (currentTimeZone != catalogTimeZone) {
// clear catalog metadata to get correct date/times when moving to a different time zone
debugPrint('$runtimeType clear catalog metadata to get correct date/times');
await metadataDb.clearMetadataEntries();
settings.catalogTimeZone = currentTimeZone;
}
eventChannel.receiveBroadcastStream().cast<Map>().listen(
(entryMap) => mediaStoreCollection.add(ImageEntry.fromMap(entryMap)),
onDone: () async {
debugPrint('$runtimeType mediastore stream done, elapsed=${_stopwatch.elapsed}');
mediaStoreCollection.updateSections(); // <50ms
// TODO reduce setup time until here
mediaStoreCollection.updateAlbums(); // <50ms
await mediaStoreCollection.loadCatalogMetadata(); // 650ms
await mediaStoreCollection.catalogEntries(); // <50ms
await mediaStoreCollection.loadAddresses(); // 350ms
await mediaStoreCollection.locateEntries(); // <50ms
debugPrint('$runtimeType setup end, elapsed=${_stopwatch.elapsed}');
},
onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),
);
// TODO split image fetch AND/OR cache fetch across sessions
await ImageFileService.getImageEntries(); // 460ms
return mediaStoreCollection;
}
@override
Widget build(BuildContext context) {
return FutureProvider<ImageCollection>(
create: (context) => _create(),
initialData: ImageCollection(entries: []),
child: child,
);
}
}