init: progressively add entries with saved dates
fullscreen: debug page
This commit is contained in:
parent
3328916c86
commit
2b2e7e31bd
14 changed files with 273 additions and 39 deletions
|
@ -27,7 +27,7 @@ public class MediaStoreStreamHandler implements EventChannel.StreamHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchAll(Activity activity) {
|
void fetchAll(Activity activity) {
|
||||||
Log.d(LOG_TAG, "fetchAll start");
|
// Log.d(LOG_TAG, "fetchAll start");
|
||||||
// Instant start = Instant.now();
|
// Instant start = Instant.now();
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
Handler handler = new Handler(Looper.getMainLooper());
|
||||||
new MediaStoreImageProvider().fetchAll(activity, (entry) -> handler.post(() -> eventSink.success(entry))); // 350ms
|
new MediaStoreImageProvider().fetchAll(activity, (entry) -> handler.post(() -> eventSink.success(entry))); // 350ms
|
||||||
|
|
|
@ -150,6 +150,9 @@ public class MediaStoreImageProvider extends ImageProvider {
|
||||||
Log.w(LOG_TAG, "failed to get size for uri=" + itemUri + ", path=" + path + ", mimeType=" + mimeType);
|
Log.w(LOG_TAG, "failed to get size for uri=" + itemUri + ", path=" + path + ", mimeType=" + mimeType);
|
||||||
} else {
|
} else {
|
||||||
newEntryHandler.handleEntry(entryMap);
|
newEntryHandler.handleEntry(entryMap);
|
||||||
|
if (entryCount % 30 == 0) {
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
entryCount++;
|
entryCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,22 @@ class CollectionSource {
|
||||||
List<ImageEntry> entries,
|
List<ImageEntry> entries,
|
||||||
}) : _rawEntries = entries ?? [];
|
}) : _rawEntries = entries ?? [];
|
||||||
|
|
||||||
|
final List<DateMetadata> savedDates = [];
|
||||||
|
|
||||||
|
Future<void> 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<void> loadCatalogMetadata() async {
|
Future<void> loadCatalogMetadata() async {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
final saved = await metadataDb.loadMetadataEntries();
|
final saved = await metadataDb.loadMetadataEntries();
|
||||||
_rawEntries.forEach((entry) {
|
_rawEntries.forEach((entry) {
|
||||||
final contentId = entry.contentId;
|
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();
|
onCatalogMetadataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +51,9 @@ class CollectionSource {
|
||||||
final saved = await metadataDb.loadAddresses();
|
final saved = await metadataDb.loadAddresses();
|
||||||
_rawEntries.forEach((entry) {
|
_rawEntries.forEach((entry) {
|
||||||
final contentId = entry.contentId;
|
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();
|
onAddressMetadataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,12 +128,11 @@ class CollectionSource {
|
||||||
sortedCities = lister((address) => address.city);
|
sortedCities = lister((address) => address.city);
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(ImageEntry entry) {
|
|
||||||
_rawEntries.add(entry);
|
|
||||||
eventBus.fire(EntryAddedEvent(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
void addAll(Iterable<ImageEntry> entries) {
|
void addAll(Iterable<ImageEntry> entries) {
|
||||||
|
entries.forEach((entry) {
|
||||||
|
final contentId = entry.contentId;
|
||||||
|
entry.catalogDateMillis = savedDates.firstWhere((metadata) => metadata.contentId == contentId, orElse: () => null)?.dateMillis;
|
||||||
|
});
|
||||||
_rawEntries.addAll(entries);
|
_rawEntries.addAll(entries);
|
||||||
eventBus.fire(const EntryAddedEvent());
|
eventBus.fire(const EntryAddedEvent());
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ class ImageEntry {
|
||||||
final int sourceDateTakenMillis;
|
final int sourceDateTakenMillis;
|
||||||
final String bucketDisplayName;
|
final String bucketDisplayName;
|
||||||
final int durationMillis;
|
final int durationMillis;
|
||||||
|
int _catalogDateMillis;
|
||||||
CatalogMetadata _catalogMetadata;
|
CatalogMetadata _catalogMetadata;
|
||||||
AddressDetails _addressDetails;
|
AddressDetails _addressDetails;
|
||||||
|
|
||||||
|
@ -134,11 +135,11 @@ class ImageEntry {
|
||||||
|
|
||||||
DateTime get bestDate {
|
DateTime get bestDate {
|
||||||
if (_bestDate == null) {
|
if (_bestDate == null) {
|
||||||
if ((_catalogMetadata?.dateMillis ?? 0) > 0) {
|
if ((_catalogDateMillis ?? 0) > 0) {
|
||||||
_bestDate = DateTime.fromMillisecondsSinceEpoch(_catalogMetadata.dateMillis);
|
_bestDate = DateTime.fromMillisecondsSinceEpoch(_catalogDateMillis);
|
||||||
} else if (sourceDateTakenMillis != null && sourceDateTakenMillis > 0) {
|
} else if ((sourceDateTakenMillis ?? 0) > 0) {
|
||||||
_bestDate = DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis);
|
_bestDate = DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis);
|
||||||
} else if (dateModifiedSecs != null && dateModifiedSecs > 0) {
|
} else if ((dateModifiedSecs ?? 0) > 0) {
|
||||||
_bestDate = DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000);
|
_bestDate = DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,13 +182,17 @@ class ImageEntry {
|
||||||
|
|
||||||
CatalogMetadata get catalogMetadata => _catalogMetadata;
|
CatalogMetadata get catalogMetadata => _catalogMetadata;
|
||||||
|
|
||||||
set catalogMetadata(CatalogMetadata newMetadata) {
|
set catalogDateMillis(int dateMillis) {
|
||||||
_catalogMetadata = newMetadata;
|
_catalogDateMillis = dateMillis;
|
||||||
_bestDate = null;
|
_bestDate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
set catalogMetadata(CatalogMetadata newMetadata) {
|
||||||
|
if (newMetadata == null) return;
|
||||||
|
catalogDateMillis = newMetadata.dateMillis;
|
||||||
|
_catalogMetadata = newMetadata;
|
||||||
_bestTitle = null;
|
_bestTitle = null;
|
||||||
if (_catalogMetadata != null) {
|
metadataChangeNotifier.notifyListeners();
|
||||||
metadataChangeNotifier.notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> catalog() async {
|
Future<void> catalog() async {
|
||||||
|
|
|
@ -1,6 +1,32 @@
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:geocoder/model.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<String, dynamic> toMap() => {
|
||||||
|
'contentId': contentId,
|
||||||
|
'dateMillis': dateMillis,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DateMetadata{contentId=$contentId, dateMillis=$dateMillis}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CatalogMetadata {
|
class CatalogMetadata {
|
||||||
final int contentId, dateMillis, videoRotation;
|
final int contentId, dateMillis, videoRotation;
|
||||||
final String xmpSubjects, xmpTitleDescription;
|
final String xmpSubjects, xmpTitleDescription;
|
||||||
|
|
|
@ -12,6 +12,7 @@ class MetadataDb {
|
||||||
|
|
||||||
Future<String> get path async => join(await getDatabasesPath(), 'metadata.db');
|
Future<String> get path async => join(await getDatabasesPath(), 'metadata.db');
|
||||||
|
|
||||||
|
static const dateTakenTable = 'dateTaken';
|
||||||
static const metadataTable = 'metadata';
|
static const metadataTable = 'metadata';
|
||||||
static const addressTable = 'address';
|
static const addressTable = 'address';
|
||||||
static const favouriteTable = 'favourites';
|
static const favouriteTable = 'favourites';
|
||||||
|
@ -23,6 +24,7 @@ class MetadataDb {
|
||||||
_database = openDatabase(
|
_database = openDatabase(
|
||||||
await path,
|
await path,
|
||||||
onCreate: (db, version) async {
|
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 $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 $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)');
|
await db.execute('CREATE TABLE $favouriteTable(contentId INTEGER PRIMARY KEY, path TEXT)');
|
||||||
|
@ -43,6 +45,25 @@ class MetadataDb {
|
||||||
await init();
|
await init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// date taken
|
||||||
|
|
||||||
|
Future<void> clearDates() async {
|
||||||
|
final db = await _database;
|
||||||
|
final count = await db.delete(dateTakenTable, where: '1');
|
||||||
|
debugPrint('$runtimeType clearDates deleted $count entries');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<DateMetadata>> 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<void> clearMetadataEntries() async {
|
Future<void> clearMetadataEntries() async {
|
||||||
final db = await _database;
|
final db = await _database;
|
||||||
final count = await db.delete(metadataTable, where: '1');
|
final count = await db.delete(metadataTable, where: '1');
|
||||||
|
@ -54,7 +75,7 @@ class MetadataDb {
|
||||||
final db = await _database;
|
final db = await _database;
|
||||||
final maps = await db.query(metadataTable);
|
final maps = await db.query(metadataTable);
|
||||||
final metadataEntries = maps.map((map) => CatalogMetadata.fromMap(map)).toList();
|
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;
|
return metadataEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,15 +84,26 @@ class MetadataDb {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
final db = await _database;
|
final db = await _database;
|
||||||
final batch = db.batch();
|
final batch = db.batch();
|
||||||
metadataEntries.where((metadata) => metadata != null).forEach((metadata) => batch.insert(
|
metadataEntries.where((metadata) => metadata != null).forEach((metadata) {
|
||||||
metadataTable,
|
if (metadata.dateMillis != 0) {
|
||||||
metadata.toMap(),
|
batch.insert(
|
||||||
|
dateTakenTable,
|
||||||
|
DateMetadata(contentId: metadata.contentId, dateMillis: metadata.dateMillis).toMap(),
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||||
));
|
);
|
||||||
|
}
|
||||||
|
batch.insert(
|
||||||
|
metadataTable,
|
||||||
|
metadata.toMap(),
|
||||||
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||||
|
);
|
||||||
|
});
|
||||||
await batch.commit(noResult: true);
|
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<void> clearAddresses() async {
|
Future<void> clearAddresses() async {
|
||||||
final db = await _database;
|
final db = await _database;
|
||||||
final count = await db.delete(addressTable, where: '1');
|
final count = await db.delete(addressTable, where: '1');
|
||||||
|
@ -83,7 +115,7 @@ class MetadataDb {
|
||||||
final db = await _database;
|
final db = await _database;
|
||||||
final maps = await db.query(addressTable);
|
final maps = await db.query(addressTable);
|
||||||
final addresses = maps.map((map) => AddressDetails.fromMap(map)).toList();
|
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;
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +130,7 @@ class MetadataDb {
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||||
));
|
));
|
||||||
await batch.commit(noResult: true);
|
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
|
// favourites
|
||||||
|
@ -114,7 +146,7 @@ class MetadataDb {
|
||||||
final db = await _database;
|
final db = await _database;
|
||||||
final maps = await db.query(favouriteTable);
|
final maps = await db.query(favouriteTable);
|
||||||
final favouriteRows = maps.map((map) => FavouriteRow.fromMap(map)).toList();
|
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;
|
return favouriteRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +161,7 @@ class MetadataDb {
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||||
));
|
));
|
||||||
await batch.commit(noResult: true);
|
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<void> removeFavourites(Iterable<FavouriteRow> favouriteRows) async {
|
Future<void> removeFavourites(Iterable<FavouriteRow> favouriteRows) async {
|
||||||
|
@ -147,6 +179,6 @@ class MetadataDb {
|
||||||
whereArgs: [id],
|
whereArgs: [id],
|
||||||
));
|
));
|
||||||
await batch.commit(noResult: true);
|
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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,6 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
return ValueListenableBuilder<PageState>(
|
return ValueListenableBuilder<PageState>(
|
||||||
valueListenable: stateNotifier,
|
valueListenable: stateNotifier,
|
||||||
builder: (context, state, child) {
|
builder: (context, state, child) {
|
||||||
debugPrint('$runtimeType builder state=$state');
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: collection.filterChangeNotifier,
|
animation: collection.filterChangeNotifier,
|
||||||
builder: (context, child) => SliverAppBar(
|
builder: (context, child) => SliverAppBar(
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
builder: (c, mqViewInsetsBottom, child) {
|
builder: (c, mqViewInsetsBottom, child) {
|
||||||
return Consumer<CollectionLens>(
|
return Consumer<CollectionLens>(
|
||||||
builder: (context, collection, child) {
|
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 sectionKeys = collection.sections.keys.toList();
|
||||||
final showHeaders = collection.showHeaders;
|
final showHeaders = collection.showHeaders;
|
||||||
return GridScaleGestureDetector(
|
return GridScaleGestureDetector(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
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/collection_source.dart';
|
||||||
import 'package:aves/model/favourite_repo.dart';
|
import 'package:aves/model/favourite_repo.dart';
|
||||||
|
@ -35,15 +37,20 @@ class MediaStoreSource {
|
||||||
if (currentTimeZone != catalogTimeZone) {
|
if (currentTimeZone != catalogTimeZone) {
|
||||||
// clear catalog metadata to get correct date/times when moving to a different time zone
|
// 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');
|
debugPrint('$runtimeType clear catalog metadata to get correct date/times');
|
||||||
|
await metadataDb.clearDates();
|
||||||
await metadataDb.clearMetadataEntries();
|
await metadataDb.clearMetadataEntries();
|
||||||
settings.catalogTimeZone = currentTimeZone;
|
settings.catalogTimeZone = currentTimeZone;
|
||||||
}
|
}
|
||||||
|
await _source.loadDates(); // 100ms for 5400 entries
|
||||||
|
|
||||||
|
var refreshCount = 10;
|
||||||
|
const refreshCountMax = 1000;
|
||||||
final allEntries = <ImageEntry>[];
|
final allEntries = <ImageEntry>[];
|
||||||
_eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
_eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
||||||
(entryMap) {
|
(entryMap) {
|
||||||
allEntries.add(ImageEntry.fromMap(entryMap));
|
allEntries.add(ImageEntry.fromMap(entryMap));
|
||||||
if (allEntries.length >= 100) {
|
if (allEntries.length >= refreshCount) {
|
||||||
|
refreshCount = min(refreshCount * 10, refreshCountMax);
|
||||||
_source.addAll(allEntries);
|
_source.addAll(allEntries);
|
||||||
allEntries.clear();
|
allEntries.clear();
|
||||||
// debugPrint('$runtimeType streamed ${_source.entries.length} entries at ${stopwatch.elapsed.inMilliseconds}ms');
|
// debugPrint('$runtimeType streamed ${_source.entries.length} entries at ${stopwatch.elapsed.inMilliseconds}ms');
|
||||||
|
@ -54,7 +61,7 @@ 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
|
||||||
await _source.loadCatalogMetadata(); // 650ms
|
await _source.loadCatalogMetadata(); // 400ms for 5400 entries
|
||||||
await _source.catalogEntries(); // <50ms
|
await _source.catalogEntries(); // <50ms
|
||||||
await _source.loadAddresses(); // 350ms
|
await _source.loadAddresses(); // 350ms
|
||||||
await _source.locateEntries(); // <50ms
|
await _source.locateEntries(); // <50ms
|
||||||
|
|
|
@ -21,6 +21,7 @@ class DebugPage extends StatefulWidget {
|
||||||
|
|
||||||
class DebugPageState extends State<DebugPage> {
|
class DebugPageState extends State<DebugPage> {
|
||||||
Future<int> _dbFileSizeLoader;
|
Future<int> _dbFileSizeLoader;
|
||||||
|
Future<List<DateMetadata>> _dbDateLoader;
|
||||||
Future<List<CatalogMetadata>> _dbMetadataLoader;
|
Future<List<CatalogMetadata>> _dbMetadataLoader;
|
||||||
Future<List<AddressDetails>> _dbAddressLoader;
|
Future<List<AddressDetails>> _dbAddressLoader;
|
||||||
Future<List<FavouriteRow>> _dbFavouritesLoader;
|
Future<List<FavouriteRow>> _dbFavouritesLoader;
|
||||||
|
@ -78,6 +79,23 @@ class DebugPageState extends State<DebugPage> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: _dbDateLoader,
|
||||||
|
builder: (context, AsyncSnapshot<List<DateMetadata>> 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(
|
FutureBuilder(
|
||||||
future: _dbMetadataLoader,
|
future: _dbMetadataLoader,
|
||||||
builder: (context, AsyncSnapshot<List<CatalogMetadata>> snapshot) {
|
builder: (context, AsyncSnapshot<List<CatalogMetadata>> snapshot) {
|
||||||
|
@ -175,6 +193,7 @@ class DebugPageState extends State<DebugPage> {
|
||||||
|
|
||||||
void _startDbReport() {
|
void _startDbReport() {
|
||||||
_dbFileSizeLoader = metadataDb.dbFileSize();
|
_dbFileSizeLoader = metadataDb.dbFileSize();
|
||||||
|
_dbDateLoader = metadataDb.loadDates();
|
||||||
_dbMetadataLoader = metadataDb.loadMetadataEntries();
|
_dbMetadataLoader = metadataDb.loadMetadataEntries();
|
||||||
_dbAddressLoader = metadataDb.loadAddresses();
|
_dbAddressLoader = metadataDb.loadAddresses();
|
||||||
_dbFavouritesLoader = metadataDb.loadFavourites();
|
_dbFavouritesLoader = metadataDb.loadFavourites();
|
||||||
|
|
115
lib/widgets/fullscreen/debug.dart
Normal file
115
lib/widgets/fullscreen/debug.dart
Normal file
|
@ -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<FullscreenDebugPage> {
|
||||||
|
Future<DateMetadata> _dbDateLoader;
|
||||||
|
Future<CatalogMetadata> _dbMetadataLoader;
|
||||||
|
Future<AddressDetails> _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<DateMetadata> 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<CatalogMetadata> 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<AddressDetails> 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(() {});
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/image_file_service.dart';
|
import 'package:aves/model/image_file_service.dart';
|
||||||
import 'package:aves/utils/android_app_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/common/image_providers/uri_image_provider.dart';
|
||||||
|
import 'package:aves/widgets/fullscreen/debug.dart';
|
||||||
import 'package:aves/widgets/fullscreen/fullscreen_actions.dart';
|
import 'package:aves/widgets/fullscreen/fullscreen_actions.dart';
|
||||||
import 'package:flushbar/flushbar.dart';
|
import 'package:flushbar/flushbar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -63,6 +64,9 @@ class FullscreenActionDelegate {
|
||||||
case FullscreenAction.share:
|
case FullscreenAction.share:
|
||||||
AndroidAppService.share(entry.uri, entry.mimeType);
|
AndroidAppService.share(entry.uri, entry.mimeType);
|
||||||
break;
|
break;
|
||||||
|
case FullscreenAction.debug:
|
||||||
|
_goToDebug(context, entry);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,4 +181,13 @@ class FullscreenActionDelegate {
|
||||||
if (newName == null || newName.isEmpty) return;
|
if (newName == null || newName.isEmpty) return;
|
||||||
_showFeedback(context, await entry.rename(newName) ? 'Done!' : 'Failed');
|
_showFeedback(context, await entry.rename(newName) ? 'Done!' : 'Failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _goToDebug(BuildContext context, ImageEntry entry) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => FullscreenDebugPage(entry: entry),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:outline_material_icons/outline_material_icons.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 {
|
class FullscreenActions {
|
||||||
static const inApp = [
|
static const inApp = [
|
||||||
|
@ -53,6 +53,8 @@ extension ExtraFullscreenAction on FullscreenAction {
|
||||||
return 'Set as…';
|
return 'Set as…';
|
||||||
case FullscreenAction.openMap:
|
case FullscreenAction.openMap:
|
||||||
return 'Show on map…';
|
return 'Show on map…';
|
||||||
|
case FullscreenAction.debug:
|
||||||
|
return 'Debug';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -83,6 +85,8 @@ extension ExtraFullscreenAction on FullscreenAction {
|
||||||
case FullscreenAction.setAs:
|
case FullscreenAction.setAs:
|
||||||
case FullscreenAction.openMap:
|
case FullscreenAction.openMap:
|
||||||
return null;
|
return null;
|
||||||
|
case FullscreenAction.debug:
|
||||||
|
return OMIcons.whatshot;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,10 @@ class FullscreenTopOverlay extends StatelessWidget {
|
||||||
...inAppActions.map(_buildPopupMenuItem),
|
...inAppActions.map(_buildPopupMenuItem),
|
||||||
const PopupMenuDivider(),
|
const PopupMenuDivider(),
|
||||||
...externalAppActions.map(_buildPopupMenuItem),
|
...externalAppActions.map(_buildPopupMenuItem),
|
||||||
|
if (kDebugMode) ...[
|
||||||
|
const PopupMenuDivider(),
|
||||||
|
_buildPopupMenuItem(FullscreenAction.debug),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
onSelected: onActionSelected,
|
onSelected: onActionSelected,
|
||||||
),
|
),
|
||||||
|
@ -109,6 +113,8 @@ class FullscreenTopOverlay extends StatelessWidget {
|
||||||
case FullscreenAction.edit:
|
case FullscreenAction.edit:
|
||||||
case FullscreenAction.setAs:
|
case FullscreenAction.setAs:
|
||||||
return true;
|
return true;
|
||||||
|
case FullscreenAction.debug:
|
||||||
|
return kDebugMode;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -153,6 +159,7 @@ class FullscreenTopOverlay extends StatelessWidget {
|
||||||
case FullscreenAction.open:
|
case FullscreenAction.open:
|
||||||
case FullscreenAction.edit:
|
case FullscreenAction.edit:
|
||||||
case FullscreenAction.setAs:
|
case FullscreenAction.setAs:
|
||||||
|
case FullscreenAction.debug:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return child != null
|
return child != null
|
||||||
|
@ -188,6 +195,7 @@ class FullscreenTopOverlay extends StatelessWidget {
|
||||||
case FullscreenAction.rotateCCW:
|
case FullscreenAction.rotateCCW:
|
||||||
case FullscreenAction.rotateCW:
|
case FullscreenAction.rotateCW:
|
||||||
case FullscreenAction.print:
|
case FullscreenAction.print:
|
||||||
|
case FullscreenAction.debug:
|
||||||
child = MenuRow(text: action.getText(), icon: action.getIcon());
|
child = MenuRow(text: action.getText(), icon: action.getIcon());
|
||||||
break;
|
break;
|
||||||
// external app actions
|
// external app actions
|
||||||
|
|
Loading…
Reference in a new issue