From e4da59a6244b1e139add6fff6c89cfc0e842f115 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 11 Aug 2019 16:32:43 +0900 Subject: [PATCH] fullscreen: added address to overlay --- lib/model/image_entry.dart | 27 +++++++-- lib/model/image_metadata.dart | 36 ++++++++++++ lib/model/metadata_db.dart | 55 +++++++++++++----- lib/model/metadata_service.dart | 2 +- lib/widgets/album/all_collection_page.dart | 4 +- lib/widgets/debug_page.dart | 56 +++++++++++++------ .../fullscreen/info/location_section.dart | 2 +- lib/widgets/fullscreen/overlay_bottom.dart | 18 ++++++ 8 files changed, 160 insertions(+), 40 deletions(-) diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index d08872633..6d19ed0dc 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/metadata_service.dart'; import 'package:flutter/material.dart'; @@ -21,7 +23,7 @@ class ImageEntry with ChangeNotifier { final String bucketDisplayName; final int durationMillis; CatalogMetadata catalogMetadata; - String addressLine, addressCountry; + AddressDetails addressDetails; ImageEntry({ this.uri, @@ -125,7 +127,7 @@ class ImageEntry with ChangeNotifier { bool get hasGps => isCataloged && catalogMetadata.latitude != null; - bool get isLocated => addressLine != null; + bool get isLocated => addressDetails != null; Tuple2 get latLng => isCataloged ? Tuple2(catalogMetadata.latitude, catalogMetadata.longitude) : null; @@ -150,8 +152,13 @@ class ImageEntry with ChangeNotifier { final addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates); if (addresses != null && addresses.length > 0) { final address = addresses.first; - addressLine = address.addressLine; - addressCountry = address.countryName; + addressDetails = AddressDetails( + contentId: contentId, + addressLine: address.addressLine, + countryName: address.countryName, + adminArea: address.adminArea, + locality: address.locality, + ); notifyListeners(); } } catch (e) { @@ -160,10 +167,20 @@ class ImageEntry with ChangeNotifier { } } + String get shortAddress { + if (!isLocated) return ''; + + // admin area examples: Seoul, Geneva, null + // locality examples: Mapo-gu, Geneva, Annecy + return LinkedHashSet.of( + [addressDetails.countryName, addressDetails.adminArea, addressDetails.locality], + ).where((part) => part != null && part.isNotEmpty).join(', '); + } + bool search(String query) { if (title.toLowerCase().contains(query)) return true; if (catalogMetadata?.xmpSubjects?.toLowerCase()?.contains(query) ?? false) return true; - if (isLocated && addressLine.toLowerCase().contains(query)) return true; + if (isLocated && addressDetails.addressLine.toLowerCase().contains(query)) return true; return false; } } diff --git a/lib/model/image_metadata.dart b/lib/model/image_metadata.dart index babe445d9..ce96d1e97 100644 --- a/lib/model/image_metadata.dart +++ b/lib/model/image_metadata.dart @@ -70,3 +70,39 @@ class OverlayMetadata { return 'OverlayMetadata{aperture=$aperture, exposureTime=$exposureTime, focalLength=$focalLength, iso=$iso}'; } } + +class AddressDetails { + final int contentId; + final String addressLine, countryName, adminArea, locality; + + AddressDetails({ + this.contentId, + this.addressLine, + this.countryName, + this.adminArea, + this.locality, + }); + + factory AddressDetails.fromMap(Map map) { + return AddressDetails( + contentId: map['contentId'], + addressLine: map['addressLine'] ?? '', + countryName: map['countryName'] ?? '', + adminArea: map['adminArea'] ?? '', + locality: map['locality'] ?? '', + ); + } + + Map toMap() => { + 'contentId': contentId, + 'addressLine': addressLine, + 'countryName': countryName, + 'adminArea': adminArea, + 'locality': locality, + }; + + @override + String toString() { + return 'AddressDetails{contentId=$contentId, addressLine=$addressLine, countryName=$countryName, adminArea=$adminArea, locality=$locality}'; + } +} diff --git a/lib/model/metadata_db.dart b/lib/model/metadata_db.dart index 8e8c54501..4b853d951 100644 --- a/lib/model/metadata_db.dart +++ b/lib/model/metadata_db.dart @@ -10,7 +10,8 @@ class MetadataDb { Future get path async => join(await getDatabasesPath(), 'metadata.db'); - static final table = 'metadata'; + static final metadataTable = 'metadata'; + static final addressTable = 'address'; MetadataDb._private(); @@ -18,10 +19,9 @@ class MetadataDb { debugPrint('$runtimeType init'); _database = openDatabase( await path, - onCreate: (db, version) { - return db.execute( - 'CREATE TABLE $table(contentId INTEGER PRIMARY KEY, dateMillis INTEGER, videoRotation INTEGER, xmpSubjects TEXT, latitude REAL, longitude REAL)', - ); + onCreate: (db, version) async { + await db.execute('CREATE TABLE $metadataTable(contentId INTEGER PRIMARY KEY, dateMillis INTEGER, videoRotation INTEGER, xmpSubjects TEXT, latitude REAL, longitude REAL)'); + await db.execute('CREATE TABLE $addressTable(contentId INTEGER PRIMARY KEY, addressLine TEXT, countryName TEXT, adminArea TEXT, locality TEXT)'); }, version: 1, ); @@ -34,28 +34,55 @@ class MetadataDb { await init(); } - Future> getAll() async { - debugPrint('$runtimeType getAll'); + Future> getAllMetadata() async { + debugPrint('$runtimeType getAllMetadata'); final db = await _database; - final maps = await db.query(table); + final maps = await db.query(metadataTable); return maps.map((map) => CatalogMetadata.fromMap(map)).toList(); } - Future get(int contentId) async { - debugPrint('$runtimeType get contentId=$contentId'); + Future getMetadata(int contentId) async { + debugPrint('$runtimeType getMetadata contentId=$contentId'); final db = await _database; - List maps = await db.query(table, where: 'contentId = ?', whereArgs: [contentId]); + List maps = await db.query(metadataTable, where: 'contentId = ?', whereArgs: [contentId]); if (maps.length > 0) { return CatalogMetadata.fromMap(maps.first); } return null; } - insert(CatalogMetadata metadata) async { -// debugPrint('$runtimeType insert metadata=$metadata'); + insertMetadata(CatalogMetadata metadata) async { +// debugPrint('$runtimeType insertMetadata metadata=$metadata'); final db = await _database; await db.insert( - table, + metadataTable, + metadata.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + Future> getAllAddresses() async { + debugPrint('$runtimeType getAllAddresses'); + final db = await _database; + final maps = await db.query(addressTable); + return maps.map((map) => AddressDetails.fromMap(map)).toList(); + } + + Future getAddress(int contentId) async { + debugPrint('$runtimeType getAddress contentId=$contentId'); + final db = await _database; + List maps = await db.query(addressTable, where: 'contentId = ?', whereArgs: [contentId]); + if (maps.length > 0) { + return AddressDetails.fromMap(maps.first); + } + return null; + } + + insertAddress(AddressDetails metadata) async { +// debugPrint('$runtimeType insertAddress metadata=$metadata'); + final db = await _database; + await db.insert( + addressTable, metadata.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); diff --git a/lib/model/metadata_service.dart b/lib/model/metadata_service.dart index 84c929495..e683ebfba 100644 --- a/lib/model/metadata_service.dart +++ b/lib/model/metadata_service.dart @@ -35,7 +35,7 @@ class MetadataService { }) as Map; result['contentId'] = entry.contentId; metadata = CatalogMetadata.fromMap(result); - metadataDb.insert(metadata); + metadataDb.insertMetadata(metadata); return metadata; } on PlatformException catch (e) { debugPrint('getCatalogMetadata failed with exception=${e.message}'); diff --git a/lib/widgets/album/all_collection_page.dart b/lib/widgets/album/all_collection_page.dart index 5faa52386..50d3cc79f 100644 --- a/lib/widgets/album/all_collection_page.dart +++ b/lib/widgets/album/all_collection_page.dart @@ -34,7 +34,9 @@ class AllCollectionPage extends StatelessWidget { return Navigator.push( context, MaterialPageRoute( - builder: (context) => DebugPage(), + builder: (context) => DebugPage( + entries: entries, + ), ), ); } diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart index 020cdea5f..5968ca0dd 100644 --- a/lib/widgets/debug_page.dart +++ b/lib/widgets/debug_page.dart @@ -1,49 +1,69 @@ +import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/metadata_db.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; class DebugPage extends StatefulWidget { + final List entries; + + const DebugPage({this.entries}); + @override State createState() => DebugPageState(); } class DebugPageState extends State { - Future> _dbLoader; + Future> _dbMetadataLoader; + Future> _dbAddressLoader; + + List get entries => widget.entries; @override void initState() { super.initState(); - _dbLoader = metadataDb.getAll(); + _dbMetadataLoader = metadataDb.getAllMetadata(); + _dbAddressLoader = metadataDb.getAllAddresses(); } @override Widget build(BuildContext context) { + final Map> byMimeTypes = groupBy(entries, (entry) => entry.mimeType); + final cataloged = entries.where((entry) => entry.isCataloged); + final withGps = cataloged.where((entry) => entry.hasGps); + final located = withGps.where((entry) => entry.isLocated); return Scaffold( appBar: AppBar( title: Text('Info'), ), body: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('Entries: ${entries.length}'), + ...byMimeTypes.keys.map((mimeType) => Text('- $mimeType: ${byMimeTypes[mimeType].length}')), + Text('Cataloged: ${cataloged.length}'), + Text('With GPS: ${withGps.length}'), + Text('With address: ${located.length}'), + Divider(), RaisedButton( onPressed: () => metadataDb.reset(), child: Text('Reset DB'), ), - Expanded( - child: FutureBuilder( - future: _dbLoader, - builder: (futureContext, AsyncSnapshot> snapshot) { - if (snapshot.hasError) return Text(snapshot.error); - if (snapshot.connectionState != ConnectionState.done) - return Center( - child: CircularProgressIndicator(), - ); - final metadata = snapshot.data; - return ListView.builder( - itemBuilder: (context, index) => Text(' $index: ${metadata[index]}'), - itemCount: metadata.length, - ); - }, - ), + FutureBuilder( + future: _dbMetadataLoader, + builder: (futureContext, AsyncSnapshot> snapshot) { + if (snapshot.hasError) return Text(snapshot.error); + if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink(); + return Text('DB metadata rows: ${snapshot.data.length}'); + }, + ), + FutureBuilder( + future: _dbAddressLoader, + builder: (futureContext, AsyncSnapshot> snapshot) { + if (snapshot.hasError) return Text(snapshot.error); + if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink(); + return Text('DB address rows: ${snapshot.data.length}'); + }, ), ], ), diff --git a/lib/widgets/fullscreen/info/location_section.dart b/lib/widgets/fullscreen/info/location_section.dart index f6e9d1005..09f738e7f 100644 --- a/lib/widgets/fullscreen/info/location_section.dart +++ b/lib/widgets/fullscreen/info/location_section.dart @@ -20,7 +20,7 @@ class LocationSection extends AnimatedWidget { if (entry.isLocated) Padding( padding: EdgeInsets.only(top: 8), - child: InfoRow('Address', entry.addressLine), + child: InfoRow('Address', entry.addressDetails.addressLine), ), ], ); diff --git a/lib/widgets/fullscreen/overlay_bottom.dart b/lib/widgets/fullscreen/overlay_bottom.dart index 12909000a..c019342b1 100644 --- a/lib/widgets/fullscreen/overlay_bottom.dart +++ b/lib/widgets/fullscreen/overlay_bottom.dart @@ -118,6 +118,24 @@ class _FullscreenBottomOverlayContent extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), + if (entry.isLocated) ...[ + SizedBox(height: 4), + SizedBox( + width: subRowWidth, + child: Row( + children: [ + Icon(Icons.place, size: 16), + SizedBox(width: 8), + Expanded( + child: Text( + entry.shortAddress, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ], SizedBox(height: 4), SizedBox( width: subRowWidth,