media store collection provider
This commit is contained in:
parent
dc14c354a8
commit
582afba3e9
7 changed files with 131 additions and 69 deletions
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -64,6 +64,7 @@ class ImageCollection with ChangeNotifier {
|
|||
]);
|
||||
break;
|
||||
}
|
||||
debugPrint('$runtimeType updateSections');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
66
lib/widgets/common/media_store_collection_provider.dart
Normal file
66
lib/widgets/common/media_store_collection_provider.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue