load metadata/address on startup
This commit is contained in:
parent
27bf3f4dad
commit
6206dbde62
6 changed files with 55 additions and 42 deletions
|
@ -47,11 +47,14 @@ class _HomePageState extends State<HomePage> {
|
||||||
await metadataDb.init();
|
await metadataDb.init();
|
||||||
|
|
||||||
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
||||||
(entryMap) => setState(() => entries.add(ImageEntry.fromMap(entryMap))),
|
(entryMap) => entries.add(ImageEntry.fromMap(entryMap)),
|
||||||
onDone: () async {
|
onDone: () async {
|
||||||
debugPrint('mediastore stream done');
|
debugPrint('mediastore stream done');
|
||||||
|
await loadCatalogMetadata();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
await catalogEntries();
|
await catalogEntries();
|
||||||
|
setState(() {});
|
||||||
|
await loadAddresses();
|
||||||
await locateEntries();
|
await locateEntries();
|
||||||
},
|
},
|
||||||
onError: (error) => debugPrint('mediastore stream error=$error'),
|
onError: (error) => debugPrint('mediastore stream error=$error'),
|
||||||
|
@ -69,10 +72,36 @@ class _HomePageState extends State<HomePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadCatalogMetadata() async {
|
||||||
|
debugPrint('$runtimeType loadCatalogMetadata start');
|
||||||
|
final start = DateTime.now();
|
||||||
|
final saved = await metadataDb.loadMetadataEntries();
|
||||||
|
entries.forEach((entry) {
|
||||||
|
final contentId = entry.contentId;
|
||||||
|
if (contentId != null) {
|
||||||
|
entry.catalogMetadata = saved.firstWhere((metadata) => metadata.contentId == contentId, orElse: () => null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
debugPrint('$runtimeType loadCatalogMetadata complete in ${DateTime.now().difference(start).inSeconds}s with ${saved.length} saved entries');
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAddresses() async {
|
||||||
|
debugPrint('$runtimeType loadAddresses start');
|
||||||
|
final start = DateTime.now();
|
||||||
|
final saved = await metadataDb.loadAddresses();
|
||||||
|
entries.forEach((entry) {
|
||||||
|
final contentId = entry.contentId;
|
||||||
|
if (contentId != null) {
|
||||||
|
entry.addressDetails = saved.firstWhere((address) => address.contentId == contentId, orElse: () => null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
debugPrint('$runtimeType loadAddresses complete in ${DateTime.now().difference(start).inSeconds}s with ${saved.length} saved entries');
|
||||||
|
}
|
||||||
|
|
||||||
catalogEntries() async {
|
catalogEntries() async {
|
||||||
debugPrint('$runtimeType catalogEntries start');
|
debugPrint('$runtimeType catalogEntries start');
|
||||||
final start = DateTime.now();
|
final start = DateTime.now();
|
||||||
final uncataloguedEntries = entries.where((entry) => !entry.isCataloged);
|
final uncataloguedEntries = entries.where((entry) => !entry.isCatalogued);
|
||||||
final newMetadata = List<CatalogMetadata>();
|
final newMetadata = List<CatalogMetadata>();
|
||||||
await Future.forEach<ImageEntry>(uncataloguedEntries, (entry) async {
|
await Future.forEach<ImageEntry>(uncataloguedEntries, (entry) async {
|
||||||
await entry.catalog();
|
await entry.catalog();
|
||||||
|
@ -82,7 +111,6 @@ class _HomePageState extends State<HomePage> {
|
||||||
|
|
||||||
// sort with more accurate date
|
// sort with more accurate date
|
||||||
entries.sort((a, b) => b.bestDate.compareTo(a.bestDate));
|
entries.sort((a, b) => b.bestDate.compareTo(a.bestDate));
|
||||||
setState(() {});
|
|
||||||
|
|
||||||
metadataDb.saveMetadata(List.unmodifiable(newMetadata));
|
metadataDb.saveMetadata(List.unmodifiable(newMetadata));
|
||||||
}
|
}
|
||||||
|
@ -100,6 +128,6 @@ class _HomePageState extends State<HomePage> {
|
||||||
newAddresses.clear();
|
newAddresses.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s with ${newAddresses.length} new addresses');
|
debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,11 +86,11 @@ class ImageEntry with ChangeNotifier {
|
||||||
|
|
||||||
bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO);
|
bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO);
|
||||||
|
|
||||||
bool get isCataloged => catalogMetadata != null;
|
bool get isCatalogued => catalogMetadata != null;
|
||||||
|
|
||||||
double get aspectRatio {
|
double get aspectRatio {
|
||||||
if (width == 0 || height == 0) return 1;
|
if (width == 0 || height == 0) return 1;
|
||||||
if (isVideo && isCataloged) {
|
if (isVideo && isCatalogued) {
|
||||||
if (catalogMetadata.videoRotation % 180 == 90) return height / width;
|
if (catalogMetadata.videoRotation % 180 == 90) return height / width;
|
||||||
}
|
}
|
||||||
return width / height;
|
return width / height;
|
||||||
|
@ -125,18 +125,18 @@ class ImageEntry with ChangeNotifier {
|
||||||
return '${d.inHours}:$twoDigitMinutes:$twoDigitSeconds';
|
return '${d.inHours}:$twoDigitMinutes:$twoDigitSeconds';
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasGps => isCataloged && catalogMetadata.latitude != null;
|
bool get hasGps => isCatalogued && catalogMetadata.latitude != null;
|
||||||
|
|
||||||
bool get isLocated => addressDetails != null;
|
bool get isLocated => addressDetails != null;
|
||||||
|
|
||||||
Tuple2<double, double> get latLng => isCataloged ? Tuple2(catalogMetadata.latitude, catalogMetadata.longitude) : null;
|
Tuple2<double, double> get latLng => isCatalogued ? Tuple2(catalogMetadata.latitude, catalogMetadata.longitude) : null;
|
||||||
|
|
||||||
String get geoUri => hasGps ? 'geo:${catalogMetadata.latitude},${catalogMetadata.longitude}' : null;
|
String get geoUri => hasGps ? 'geo:${catalogMetadata.latitude},${catalogMetadata.longitude}' : null;
|
||||||
|
|
||||||
List<String> get xmpSubjects => catalogMetadata?.xmpSubjects?.split(';')?.where((tag) => tag.isNotEmpty)?.toList() ?? [];
|
List<String> get xmpSubjects => catalogMetadata?.xmpSubjects?.split(';')?.where((tag) => tag.isNotEmpty)?.toList() ?? [];
|
||||||
|
|
||||||
catalog() async {
|
catalog() async {
|
||||||
if (isCataloged) return;
|
if (isCatalogued) return;
|
||||||
catalogMetadata = await MetadataService.getCatalogMetadata(this);
|
catalogMetadata = await MetadataService.getCatalogMetadata(this);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,21 +34,13 @@ class MetadataDb {
|
||||||
await init();
|
await init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<CatalogMetadata>> getAllMetadata() async {
|
Future<List<CatalogMetadata>> loadMetadataEntries() async {
|
||||||
debugPrint('$runtimeType getAllMetadata');
|
final start = DateTime.now();
|
||||||
final db = await _database;
|
final db = await _database;
|
||||||
final maps = await db.query(metadataTable);
|
final maps = await db.query(metadataTable);
|
||||||
return maps.map((map) => CatalogMetadata.fromMap(map)).toList();
|
final metadataEntries = maps.map((map) => CatalogMetadata.fromMap(map)).toList();
|
||||||
}
|
debugPrint('$runtimeType loadMetadataEntries complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries');
|
||||||
|
return metadataEntries;
|
||||||
Future<CatalogMetadata> getMetadata(int contentId) async {
|
|
||||||
debugPrint('$runtimeType getMetadata contentId=$contentId');
|
|
||||||
final db = await _database;
|
|
||||||
List<Map> maps = await db.query(metadataTable, where: 'contentId = ?', whereArgs: [contentId]);
|
|
||||||
if (maps.length > 0) {
|
|
||||||
return CatalogMetadata.fromMap(maps.first);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMetadata(Iterable<CatalogMetadata> metadataEntries) async {
|
saveMetadata(Iterable<CatalogMetadata> metadataEntries) async {
|
||||||
|
@ -65,21 +57,13 @@ class MetadataDb {
|
||||||
debugPrint('$runtimeType saveMetadata complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries');
|
debugPrint('$runtimeType saveMetadata complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<AddressDetails>> getAllAddresses() async {
|
Future<List<AddressDetails>> loadAddresses() async {
|
||||||
debugPrint('$runtimeType getAllAddresses');
|
final start = DateTime.now();
|
||||||
final db = await _database;
|
final db = await _database;
|
||||||
final maps = await db.query(addressTable);
|
final maps = await db.query(addressTable);
|
||||||
return maps.map((map) => AddressDetails.fromMap(map)).toList();
|
final addresses = maps.map((map) => AddressDetails.fromMap(map)).toList();
|
||||||
}
|
debugPrint('$runtimeType loadAddresses complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${addresses.length} entries');
|
||||||
|
return addresses;
|
||||||
Future<AddressDetails> getAddress(int contentId) async {
|
|
||||||
debugPrint('$runtimeType getAddress contentId=$contentId');
|
|
||||||
final db = await _database;
|
|
||||||
List<Map> maps = await db.query(addressTable, where: 'contentId = ?', whereArgs: [contentId]);
|
|
||||||
if (maps.length > 0) {
|
|
||||||
return AddressDetails.fromMap(maps.first);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAddresses(Iterable<AddressDetails> addresses) async {
|
saveAddresses(Iterable<AddressDetails> addresses) async {
|
||||||
|
|
|
@ -22,15 +22,15 @@ class DebugPageState extends State<DebugPage> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_dbMetadataLoader = metadataDb.getAllMetadata();
|
_dbMetadataLoader = metadataDb.loadMetadataEntries();
|
||||||
_dbAddressLoader = metadataDb.getAllAddresses();
|
_dbAddressLoader = metadataDb.loadAddresses();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Map<String, List<ImageEntry>> byMimeTypes = groupBy(entries, (entry) => entry.mimeType);
|
final Map<String, List<ImageEntry>> byMimeTypes = groupBy(entries, (entry) => entry.mimeType);
|
||||||
final cataloged = entries.where((entry) => entry.isCataloged);
|
final catalogued = entries.where((entry) => entry.isCatalogued);
|
||||||
final withGps = cataloged.where((entry) => entry.hasGps);
|
final withGps = catalogued.where((entry) => entry.hasGps);
|
||||||
final located = withGps.where((entry) => entry.isLocated);
|
final located = withGps.where((entry) => entry.isLocated);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -41,7 +41,7 @@ class DebugPageState extends State<DebugPage> {
|
||||||
children: [
|
children: [
|
||||||
Text('Entries: ${entries.length}'),
|
Text('Entries: ${entries.length}'),
|
||||||
...byMimeTypes.keys.map((mimeType) => Text('- $mimeType: ${byMimeTypes[mimeType].length}')),
|
...byMimeTypes.keys.map((mimeType) => Text('- $mimeType: ${byMimeTypes[mimeType].length}')),
|
||||||
Text('Cataloged: ${cataloged.length}'),
|
Text('Catalogued: ${catalogued.length}'),
|
||||||
Text('With GPS: ${withGps.length}'),
|
Text('With GPS: ${withGps.length}'),
|
||||||
Text('With address: ${located.length}'),
|
Text('With address: ${located.length}'),
|
||||||
Divider(),
|
Divider(),
|
||||||
|
|
|
@ -41,6 +41,7 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
|
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
|
@ -55,7 +56,7 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
|
||||||
markers: [
|
markers: [
|
||||||
Marker(
|
Marker(
|
||||||
markerId: MarkerId(widget.markerId),
|
markerId: MarkerId(widget.markerId),
|
||||||
icon: BitmapDescriptor.defaultMarker,
|
icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
|
||||||
position: widget.latLng,
|
position: widget.latLng,
|
||||||
)
|
)
|
||||||
].toSet(),
|
].toSet(),
|
||||||
|
|
|
@ -21,7 +21,7 @@ class XmpTagSection extends AnimatedWidget {
|
||||||
.map((tag) => Padding(
|
.map((tag) => Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 4.0),
|
padding: EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
child: Chip(
|
child: Chip(
|
||||||
backgroundColor: Colors.indigo,
|
backgroundColor: Theme.of(context).accentColor,
|
||||||
label: Text(tag),
|
label: Text(tag),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
Loading…
Reference in a new issue