From 2b2e7e31bd0dd43530217e1ac127ca665e04bc47 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 8 Apr 2020 12:32:18 +0900 Subject: [PATCH] init: progressively add entries with saved dates fullscreen: debug page --- .../MediaStoreStreamHandler.java | 2 +- .../provider/MediaStoreImageProvider.java | 3 + lib/model/collection_source.dart | 29 +++-- lib/model/image_entry.dart | 23 ++-- lib/model/image_metadata.dart | 26 ++++ lib/model/metadata_db.dart | 54 ++++++-- lib/widgets/album/collection_app_bar.dart | 1 - lib/widgets/album/thumbnail_collection.dart | 2 +- .../media_store_collection_provider.dart | 11 +- lib/widgets/debug_page.dart | 19 +++ lib/widgets/fullscreen/debug.dart | 115 ++++++++++++++++++ .../fullscreen_action_delegate.dart | 13 ++ .../fullscreen/fullscreen_actions.dart | 6 +- lib/widgets/fullscreen/overlay/top.dart | 8 ++ 14 files changed, 273 insertions(+), 39 deletions(-) create mode 100644 lib/widgets/fullscreen/debug.dart diff --git a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MediaStoreStreamHandler.java b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MediaStoreStreamHandler.java index 265910c7e..21717514b 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MediaStoreStreamHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MediaStoreStreamHandler.java @@ -27,7 +27,7 @@ public class MediaStoreStreamHandler implements EventChannel.StreamHandler { } void fetchAll(Activity activity) { - Log.d(LOG_TAG, "fetchAll start"); +// Log.d(LOG_TAG, "fetchAll start"); // Instant start = Instant.now(); Handler handler = new Handler(Looper.getMainLooper()); new MediaStoreImageProvider().fetchAll(activity, (entry) -> handler.post(() -> eventSink.success(entry))); // 350ms diff --git a/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java b/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java index fa3793cfa..e38ddce9f 100644 --- a/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java +++ b/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java @@ -150,6 +150,9 @@ public class MediaStoreImageProvider extends ImageProvider { Log.w(LOG_TAG, "failed to get size for uri=" + itemUri + ", path=" + path + ", mimeType=" + mimeType); } else { newEntryHandler.handleEntry(entryMap); + if (entryCount % 30 == 0) { + Thread.sleep(10); + } entryCount++; } } diff --git a/lib/model/collection_source.dart b/lib/model/collection_source.dart index 4b889bed6..50fcaa0f9 100644 --- a/lib/model/collection_source.dart +++ b/lib/model/collection_source.dart @@ -27,16 +27,22 @@ class CollectionSource { List entries, }) : _rawEntries = entries ?? []; + final List savedDates = []; + + Future loadDates() async { + final stopwatch = Stopwatch()..start(); + savedDates.addAll(await metadataDb.loadDates()); + debugPrint('$runtimeType loadDates complete in ${stopwatch.elapsed.inMilliseconds}ms for ${savedDates.length} saved entries'); + } + Future loadCatalogMetadata() async { final stopwatch = Stopwatch()..start(); final saved = await metadataDb.loadMetadataEntries(); _rawEntries.forEach((entry) { final contentId = entry.contentId; - if (contentId != null) { - entry.catalogMetadata = saved.firstWhere((metadata) => metadata.contentId == contentId, orElse: () => null); - } + entry.catalogMetadata = saved.firstWhere((metadata) => metadata.contentId == contentId, orElse: () => null); }); - debugPrint('$runtimeType loadCatalogMetadata complete in ${stopwatch.elapsed.inMilliseconds}ms with ${saved.length} saved entries'); + debugPrint('$runtimeType loadCatalogMetadata complete in ${stopwatch.elapsed.inMilliseconds}ms for ${saved.length} saved entries'); onCatalogMetadataChanged(); } @@ -45,11 +51,9 @@ class CollectionSource { final saved = await metadataDb.loadAddresses(); _rawEntries.forEach((entry) { final contentId = entry.contentId; - if (contentId != null) { - entry.addressDetails = saved.firstWhere((address) => address.contentId == contentId, orElse: () => null); - } + entry.addressDetails = saved.firstWhere((address) => address.contentId == contentId, orElse: () => null); }); - debugPrint('$runtimeType loadAddresses complete in ${stopwatch.elapsed.inMilliseconds}ms with ${saved.length} saved entries'); + debugPrint('$runtimeType loadAddresses complete in ${stopwatch.elapsed.inMilliseconds}ms for ${saved.length} saved entries'); onAddressMetadataChanged(); } @@ -124,12 +128,11 @@ class CollectionSource { sortedCities = lister((address) => address.city); } - void add(ImageEntry entry) { - _rawEntries.add(entry); - eventBus.fire(EntryAddedEvent(entry)); - } - void addAll(Iterable entries) { + entries.forEach((entry) { + final contentId = entry.contentId; + entry.catalogDateMillis = savedDates.firstWhere((metadata) => metadata.contentId == contentId, orElse: () => null)?.dateMillis; + }); _rawEntries.addAll(entries); eventBus.fire(const EntryAddedEvent()); } diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index 34db4140e..d6c1b9393 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -27,6 +27,7 @@ class ImageEntry { final int sourceDateTakenMillis; final String bucketDisplayName; final int durationMillis; + int _catalogDateMillis; CatalogMetadata _catalogMetadata; AddressDetails _addressDetails; @@ -134,11 +135,11 @@ class ImageEntry { DateTime get bestDate { if (_bestDate == null) { - if ((_catalogMetadata?.dateMillis ?? 0) > 0) { - _bestDate = DateTime.fromMillisecondsSinceEpoch(_catalogMetadata.dateMillis); - } else if (sourceDateTakenMillis != null && sourceDateTakenMillis > 0) { + if ((_catalogDateMillis ?? 0) > 0) { + _bestDate = DateTime.fromMillisecondsSinceEpoch(_catalogDateMillis); + } else if ((sourceDateTakenMillis ?? 0) > 0) { _bestDate = DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis); - } else if (dateModifiedSecs != null && dateModifiedSecs > 0) { + } else if ((dateModifiedSecs ?? 0) > 0) { _bestDate = DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000); } } @@ -181,13 +182,17 @@ class ImageEntry { CatalogMetadata get catalogMetadata => _catalogMetadata; - set catalogMetadata(CatalogMetadata newMetadata) { - _catalogMetadata = newMetadata; + set catalogDateMillis(int dateMillis) { + _catalogDateMillis = dateMillis; _bestDate = null; + } + + set catalogMetadata(CatalogMetadata newMetadata) { + if (newMetadata == null) return; + catalogDateMillis = newMetadata.dateMillis; + _catalogMetadata = newMetadata; _bestTitle = null; - if (_catalogMetadata != null) { - metadataChangeNotifier.notifyListeners(); - } + metadataChangeNotifier.notifyListeners(); } Future catalog() async { diff --git a/lib/model/image_metadata.dart b/lib/model/image_metadata.dart index 8e139fef1..81f7dd9c6 100644 --- a/lib/model/image_metadata.dart +++ b/lib/model/image_metadata.dart @@ -1,6 +1,32 @@ import 'package:flutter/widgets.dart'; import 'package:geocoder/model.dart'; +class DateMetadata { + final int contentId, dateMillis; + + DateMetadata({ + this.contentId, + this.dateMillis, + }); + + factory DateMetadata.fromMap(Map map) { + return DateMetadata( + contentId: map['contentId'], + dateMillis: map['dateMillis'] ?? 0, + ); + } + + Map toMap() => { + 'contentId': contentId, + 'dateMillis': dateMillis, + }; + + @override + String toString() { + return 'DateMetadata{contentId=$contentId, dateMillis=$dateMillis}'; + } +} + class CatalogMetadata { final int contentId, dateMillis, videoRotation; final String xmpSubjects, xmpTitleDescription; diff --git a/lib/model/metadata_db.dart b/lib/model/metadata_db.dart index 1c92dd099..d65e6e845 100644 --- a/lib/model/metadata_db.dart +++ b/lib/model/metadata_db.dart @@ -12,6 +12,7 @@ class MetadataDb { Future get path async => join(await getDatabasesPath(), 'metadata.db'); + static const dateTakenTable = 'dateTaken'; static const metadataTable = 'metadata'; static const addressTable = 'address'; static const favouriteTable = 'favourites'; @@ -23,6 +24,7 @@ class MetadataDb { _database = openDatabase( await path, onCreate: (db, version) async { + await db.execute('CREATE TABLE $dateTakenTable(contentId INTEGER PRIMARY KEY, dateMillis INTEGER)'); await db.execute('CREATE TABLE $metadataTable(contentId INTEGER PRIMARY KEY, dateMillis INTEGER, videoRotation INTEGER, xmpSubjects TEXT, xmpTitleDescription TEXT, latitude REAL, longitude REAL)'); await db.execute('CREATE TABLE $addressTable(contentId INTEGER PRIMARY KEY, addressLine TEXT, countryName TEXT, adminArea TEXT, locality TEXT)'); await db.execute('CREATE TABLE $favouriteTable(contentId INTEGER PRIMARY KEY, path TEXT)'); @@ -43,6 +45,25 @@ class MetadataDb { await init(); } + // date taken + + Future clearDates() async { + final db = await _database; + final count = await db.delete(dateTakenTable, where: '1'); + debugPrint('$runtimeType clearDates deleted $count entries'); + } + + Future> loadDates() async { +// final stopwatch = Stopwatch()..start(); + final db = await _database; + final maps = await db.query(dateTakenTable); + final metadataEntries = maps.map((map) => DateMetadata.fromMap(map)).toList(); +// debugPrint('$runtimeType loadDates complete in ${stopwatch.elapsed.inMilliseconds}ms for ${metadataEntries.length} entries'); + return metadataEntries; + } + + // catalog metadata + Future clearMetadataEntries() async { final db = await _database; final count = await db.delete(metadataTable, where: '1'); @@ -54,7 +75,7 @@ class MetadataDb { final db = await _database; final maps = await db.query(metadataTable); final metadataEntries = maps.map((map) => CatalogMetadata.fromMap(map)).toList(); -// debugPrint('$runtimeType loadMetadataEntries complete in ${stopwatch.elapsed.inMilliseconds}ms with ${metadataEntries.length} entries'); +// debugPrint('$runtimeType loadMetadataEntries complete in ${stopwatch.elapsed.inMilliseconds}ms for ${metadataEntries.length} entries'); return metadataEntries; } @@ -63,15 +84,26 @@ class MetadataDb { final stopwatch = Stopwatch()..start(); final db = await _database; final batch = db.batch(); - metadataEntries.where((metadata) => metadata != null).forEach((metadata) => batch.insert( - metadataTable, - metadata.toMap(), + metadataEntries.where((metadata) => metadata != null).forEach((metadata) { + if (metadata.dateMillis != 0) { + batch.insert( + dateTakenTable, + DateMetadata(contentId: metadata.contentId, dateMillis: metadata.dateMillis).toMap(), conflictAlgorithm: ConflictAlgorithm.replace, - )); + ); + } + batch.insert( + metadataTable, + metadata.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + }); await batch.commit(noResult: true); - debugPrint('$runtimeType saveMetadata complete in ${stopwatch.elapsed.inMilliseconds}ms with ${metadataEntries.length} entries'); + debugPrint('$runtimeType saveMetadata complete in ${stopwatch.elapsed.inMilliseconds}ms for ${metadataEntries.length} entries'); } + // address + Future clearAddresses() async { final db = await _database; final count = await db.delete(addressTable, where: '1'); @@ -83,7 +115,7 @@ class MetadataDb { final db = await _database; final maps = await db.query(addressTable); final addresses = maps.map((map) => AddressDetails.fromMap(map)).toList(); -// debugPrint('$runtimeType loadAddresses complete in ${stopwatch.elapsed.inMilliseconds}ms with ${addresses.length} entries'); +// debugPrint('$runtimeType loadAddresses complete in ${stopwatch.elapsed.inMilliseconds}ms for ${addresses.length} entries'); return addresses; } @@ -98,7 +130,7 @@ class MetadataDb { conflictAlgorithm: ConflictAlgorithm.replace, )); await batch.commit(noResult: true); - debugPrint('$runtimeType saveAddresses complete in ${stopwatch.elapsed.inMilliseconds}ms with ${addresses.length} entries'); + debugPrint('$runtimeType saveAddresses complete in ${stopwatch.elapsed.inMilliseconds}ms for ${addresses.length} entries'); } // favourites @@ -114,7 +146,7 @@ class MetadataDb { final db = await _database; final maps = await db.query(favouriteTable); final favouriteRows = maps.map((map) => FavouriteRow.fromMap(map)).toList(); -// debugPrint('$runtimeType loadFavourites complete in ${stopwatch.elapsed.inMilliseconds}ms with ${favouriteRows.length} entries'); +// debugPrint('$runtimeType loadFavourites complete in ${stopwatch.elapsed.inMilliseconds}ms for ${favouriteRows.length} entries'); return favouriteRows; } @@ -129,7 +161,7 @@ class MetadataDb { conflictAlgorithm: ConflictAlgorithm.replace, )); await batch.commit(noResult: true); -// debugPrint('$runtimeType addFavourites complete in ${stopwatch.elapsed.inMilliseconds}ms with ${favouriteRows.length} entries'); +// debugPrint('$runtimeType addFavourites complete in ${stopwatch.elapsed.inMilliseconds}ms for ${favouriteRows.length} entries'); } Future removeFavourites(Iterable favouriteRows) async { @@ -147,6 +179,6 @@ class MetadataDb { whereArgs: [id], )); await batch.commit(noResult: true); -// debugPrint('$runtimeType removeFavourites complete in ${stopwatch.elapsed.inMilliseconds}ms with ${favouriteRows.length} entries'); +// debugPrint('$runtimeType removeFavourites complete in ${stopwatch.elapsed.inMilliseconds}ms for ${favouriteRows.length} entries'); } } diff --git a/lib/widgets/album/collection_app_bar.dart b/lib/widgets/album/collection_app_bar.dart index b9c6d5b7c..8acd3f2e1 100644 --- a/lib/widgets/album/collection_app_bar.dart +++ b/lib/widgets/album/collection_app_bar.dart @@ -80,7 +80,6 @@ class _CollectionAppBarState extends State with SingleTickerPr return ValueListenableBuilder( valueListenable: stateNotifier, builder: (context, state, child) { - debugPrint('$runtimeType builder state=$state'); return AnimatedBuilder( animation: collection.filterChangeNotifier, builder: (context, child) => SliverAppBar( diff --git a/lib/widgets/album/thumbnail_collection.dart b/lib/widgets/album/thumbnail_collection.dart index 956f47e26..c361133aa 100644 --- a/lib/widgets/album/thumbnail_collection.dart +++ b/lib/widgets/album/thumbnail_collection.dart @@ -33,7 +33,7 @@ class ThumbnailCollection extends StatelessWidget { builder: (c, mqViewInsetsBottom, child) { return Consumer( builder: (context, collection, child) { - debugPrint('$runtimeType collection builder entries=${collection.entryCount}'); +// debugPrint('$runtimeType collection builder entries=${collection.entryCount}'); final sectionKeys = collection.sections.keys.toList(); final showHeaders = collection.showHeaders; return GridScaleGestureDetector( diff --git a/lib/widgets/common/data_providers/media_store_collection_provider.dart b/lib/widgets/common/data_providers/media_store_collection_provider.dart index 43211b437..bc229e5c5 100644 --- a/lib/widgets/common/data_providers/media_store_collection_provider.dart +++ b/lib/widgets/common/data_providers/media_store_collection_provider.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_source.dart'; import 'package:aves/model/favourite_repo.dart'; @@ -35,15 +37,20 @@ class MediaStoreSource { 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.clearDates(); await metadataDb.clearMetadataEntries(); settings.catalogTimeZone = currentTimeZone; } + await _source.loadDates(); // 100ms for 5400 entries + var refreshCount = 10; + const refreshCountMax = 1000; final allEntries = []; _eventChannel.receiveBroadcastStream().cast().listen( (entryMap) { allEntries.add(ImageEntry.fromMap(entryMap)); - if (allEntries.length >= 100) { + if (allEntries.length >= refreshCount) { + refreshCount = min(refreshCount * 10, refreshCountMax); _source.addAll(allEntries); allEntries.clear(); // debugPrint('$runtimeType streamed ${_source.entries.length} entries at ${stopwatch.elapsed.inMilliseconds}ms'); @@ -54,7 +61,7 @@ class MediaStoreSource { _source.addAll(allEntries); // TODO reduce setup time until here _source.updateAlbums(); // <50ms - await _source.loadCatalogMetadata(); // 650ms + await _source.loadCatalogMetadata(); // 400ms for 5400 entries await _source.catalogEntries(); // <50ms await _source.loadAddresses(); // 350ms await _source.locateEntries(); // <50ms diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart index b1fbf4774..a0dd00ca6 100644 --- a/lib/widgets/debug_page.dart +++ b/lib/widgets/debug_page.dart @@ -21,6 +21,7 @@ class DebugPage extends StatefulWidget { class DebugPageState extends State { Future _dbFileSizeLoader; + Future> _dbDateLoader; Future> _dbMetadataLoader; Future> _dbAddressLoader; Future> _dbFavouritesLoader; @@ -78,6 +79,23 @@ class DebugPageState extends State { ); }, ), + FutureBuilder( + future: _dbDateLoader, + builder: (context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) return Text(snapshot.error.toString()); + if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); + return Row( + children: [ + Text('DB date rows: ${snapshot.data.length}'), + const Spacer(), + RaisedButton( + onPressed: () => metadataDb.clearDates().then((_) => _startDbReport()), + child: const Text('Clear'), + ), + ], + ); + }, + ), FutureBuilder( future: _dbMetadataLoader, builder: (context, AsyncSnapshot> snapshot) { @@ -175,6 +193,7 @@ class DebugPageState extends State { void _startDbReport() { _dbFileSizeLoader = metadataDb.dbFileSize(); + _dbDateLoader = metadataDb.loadDates(); _dbMetadataLoader = metadataDb.loadMetadataEntries(); _dbAddressLoader = metadataDb.loadAddresses(); _dbFavouritesLoader = metadataDb.loadFavourites(); diff --git a/lib/widgets/fullscreen/debug.dart b/lib/widgets/fullscreen/debug.dart new file mode 100644 index 000000000..c1380dc28 --- /dev/null +++ b/lib/widgets/fullscreen/debug.dart @@ -0,0 +1,115 @@ +import 'package:aves/model/image_entry.dart'; +import 'package:aves/model/image_metadata.dart'; +import 'package:aves/model/metadata_db.dart'; +import 'package:aves/widgets/fullscreen/info/info_page.dart'; +import 'package:flutter/material.dart'; + +class FullscreenDebugPage extends StatefulWidget { + final ImageEntry entry; + + const FullscreenDebugPage({@required this.entry}); + + @override + _FullscreenDebugPageState createState() => _FullscreenDebugPageState(); +} + +class _FullscreenDebugPageState extends State { + Future _dbDateLoader; + Future _dbMetadataLoader; + Future _dbAddressLoader; + + int get contentId => widget.entry.contentId; + + @override + void initState() { + super.initState(); + _startDbReport(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Debug'), + ), + body: SafeArea( + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + FutureBuilder( + future: _dbDateLoader, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.hasError) return Text(snapshot.error.toString()); + if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); + final data = snapshot.data; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('DB date:${data == null ? ' no row' : ''}'), + if (data != null) + InfoRowGroup({ + 'dateMillis': '${data.dateMillis}', + }), + ], + ); + }, + ), + const SizedBox(height: 16), + FutureBuilder( + future: _dbMetadataLoader, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.hasError) return Text(snapshot.error.toString()); + if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); + final data = snapshot.data; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('DB metadata:${data == null ? ' no row' : ''}'), + if (data != null) + InfoRowGroup({ + 'dateMillis': '${data.dateMillis}', + 'videoRotation': '${data.videoRotation}', + 'latitude': '${data.latitude}', + 'longitude': '${data.longitude}', + 'xmpSubjects': '${data.xmpSubjects}', + 'xmpTitleDescription': '${data.xmpTitleDescription}', + }), + ], + ); + }, + ), + const SizedBox(height: 16), + FutureBuilder( + future: _dbAddressLoader, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.hasError) return Text(snapshot.error.toString()); + if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); + final data = snapshot.data; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('DB address:${data == null ? ' no row' : ''}'), + if (data != null) + InfoRowGroup({ + 'dateMillis': '${data.addressLine}', + 'countryName': '${data.countryName}', + 'adminArea': '${data.adminArea}', + 'locality': '${data.locality}', + }), + ], + ); + }, + ), + ], + ), + ), + ); + } + + void _startDbReport() { + _dbDateLoader = metadataDb.loadDates().then((values) => values.firstWhere((row) => row.contentId == contentId, orElse: () => null)); + _dbMetadataLoader = metadataDb.loadMetadataEntries().then((values) => values.firstWhere((row) => row.contentId == contentId, orElse: () => null)); + _dbAddressLoader = metadataDb.loadAddresses().then((values) => values.firstWhere((row) => row.contentId == contentId, orElse: () => null)); + setState(() {}); + } +} diff --git a/lib/widgets/fullscreen/fullscreen_action_delegate.dart b/lib/widgets/fullscreen/fullscreen_action_delegate.dart index 6c4376ebe..0722800b9 100644 --- a/lib/widgets/fullscreen/fullscreen_action_delegate.dart +++ b/lib/widgets/fullscreen/fullscreen_action_delegate.dart @@ -5,6 +5,7 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_file_service.dart'; import 'package:aves/utils/android_app_service.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; +import 'package:aves/widgets/fullscreen/debug.dart'; import 'package:aves/widgets/fullscreen/fullscreen_actions.dart'; import 'package:flushbar/flushbar.dart'; import 'package:flutter/material.dart'; @@ -63,6 +64,9 @@ class FullscreenActionDelegate { case FullscreenAction.share: AndroidAppService.share(entry.uri, entry.mimeType); break; + case FullscreenAction.debug: + _goToDebug(context, entry); + break; } } @@ -177,4 +181,13 @@ class FullscreenActionDelegate { if (newName == null || newName.isEmpty) return; _showFeedback(context, await entry.rename(newName) ? 'Done!' : 'Failed'); } + + void _goToDebug(BuildContext context, ImageEntry entry) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FullscreenDebugPage(entry: entry), + ), + ); + } } diff --git a/lib/widgets/fullscreen/fullscreen_actions.dart b/lib/widgets/fullscreen/fullscreen_actions.dart index 7b3f07a31..781011bcc 100644 --- a/lib/widgets/fullscreen/fullscreen_actions.dart +++ b/lib/widgets/fullscreen/fullscreen_actions.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; -enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share, toggleFavourite } +enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share, toggleFavourite, debug } class FullscreenActions { static const inApp = [ @@ -53,6 +53,8 @@ extension ExtraFullscreenAction on FullscreenAction { return 'Set as…'; case FullscreenAction.openMap: return 'Show on map…'; + case FullscreenAction.debug: + return 'Debug'; } return null; } @@ -83,6 +85,8 @@ extension ExtraFullscreenAction on FullscreenAction { case FullscreenAction.setAs: case FullscreenAction.openMap: return null; + case FullscreenAction.debug: + return OMIcons.whatshot; } return null; } diff --git a/lib/widgets/fullscreen/overlay/top.dart b/lib/widgets/fullscreen/overlay/top.dart index 8f7a925fa..940c27d17 100644 --- a/lib/widgets/fullscreen/overlay/top.dart +++ b/lib/widgets/fullscreen/overlay/top.dart @@ -75,6 +75,10 @@ class FullscreenTopOverlay extends StatelessWidget { ...inAppActions.map(_buildPopupMenuItem), const PopupMenuDivider(), ...externalAppActions.map(_buildPopupMenuItem), + if (kDebugMode) ...[ + const PopupMenuDivider(), + _buildPopupMenuItem(FullscreenAction.debug), + ] ], onSelected: onActionSelected, ), @@ -109,6 +113,8 @@ class FullscreenTopOverlay extends StatelessWidget { case FullscreenAction.edit: case FullscreenAction.setAs: return true; + case FullscreenAction.debug: + return kDebugMode; } return false; } @@ -153,6 +159,7 @@ class FullscreenTopOverlay extends StatelessWidget { case FullscreenAction.open: case FullscreenAction.edit: case FullscreenAction.setAs: + case FullscreenAction.debug: break; } return child != null @@ -188,6 +195,7 @@ class FullscreenTopOverlay extends StatelessWidget { case FullscreenAction.rotateCCW: case FullscreenAction.rotateCW: case FullscreenAction.print: + case FullscreenAction.debug: child = MenuRow(text: action.getText(), icon: action.getIcon()); break; // external app actions