fullscreen: added address to overlay

This commit is contained in:
Thibault Deckers 2019-08-11 16:32:43 +09:00
parent 05af913d86
commit e4da59a624
8 changed files with 160 additions and 40 deletions

View file

@ -1,3 +1,5 @@
import 'dart:collection';
import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/metadata_service.dart'; import 'package:aves/model/metadata_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -21,7 +23,7 @@ class ImageEntry with ChangeNotifier {
final String bucketDisplayName; final String bucketDisplayName;
final int durationMillis; final int durationMillis;
CatalogMetadata catalogMetadata; CatalogMetadata catalogMetadata;
String addressLine, addressCountry; AddressDetails addressDetails;
ImageEntry({ ImageEntry({
this.uri, this.uri,
@ -125,7 +127,7 @@ class ImageEntry with ChangeNotifier {
bool get hasGps => isCataloged && catalogMetadata.latitude != null; bool get hasGps => isCataloged && catalogMetadata.latitude != null;
bool get isLocated => addressLine != null; bool get isLocated => addressDetails != null;
Tuple2<double, double> get latLng => isCataloged ? Tuple2(catalogMetadata.latitude, catalogMetadata.longitude) : null; Tuple2<double, double> get latLng => isCataloged ? Tuple2(catalogMetadata.latitude, catalogMetadata.longitude) : null;
@ -150,8 +152,13 @@ class ImageEntry with ChangeNotifier {
final addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates); final addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates);
if (addresses != null && addresses.length > 0) { if (addresses != null && addresses.length > 0) {
final address = addresses.first; final address = addresses.first;
addressLine = address.addressLine; addressDetails = AddressDetails(
addressCountry = address.countryName; contentId: contentId,
addressLine: address.addressLine,
countryName: address.countryName,
adminArea: address.adminArea,
locality: address.locality,
);
notifyListeners(); notifyListeners();
} }
} catch (e) { } 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) { bool search(String query) {
if (title.toLowerCase().contains(query)) return true; if (title.toLowerCase().contains(query)) return true;
if (catalogMetadata?.xmpSubjects?.toLowerCase()?.contains(query) ?? false) 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; return false;
} }
} }

View file

@ -70,3 +70,39 @@ class OverlayMetadata {
return 'OverlayMetadata{aperture=$aperture, exposureTime=$exposureTime, focalLength=$focalLength, iso=$iso}'; 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<String, dynamic> 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}';
}
}

View file

@ -10,7 +10,8 @@ class MetadataDb {
Future<String> get path async => join(await getDatabasesPath(), 'metadata.db'); Future<String> get path async => join(await getDatabasesPath(), 'metadata.db');
static final table = 'metadata'; static final metadataTable = 'metadata';
static final addressTable = 'address';
MetadataDb._private(); MetadataDb._private();
@ -18,10 +19,9 @@ class MetadataDb {
debugPrint('$runtimeType init'); debugPrint('$runtimeType init');
_database = openDatabase( _database = openDatabase(
await path, await path,
onCreate: (db, version) { onCreate: (db, version) async {
return db.execute( await db.execute('CREATE TABLE $metadataTable(contentId INTEGER PRIMARY KEY, dateMillis INTEGER, videoRotation INTEGER, xmpSubjects TEXT, latitude REAL, longitude REAL)');
'CREATE TABLE $table(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, version: 1,
); );
@ -34,28 +34,55 @@ class MetadataDb {
await init(); await init();
} }
Future<List<CatalogMetadata>> getAll() async { Future<List<CatalogMetadata>> getAllMetadata() async {
debugPrint('$runtimeType getAll'); debugPrint('$runtimeType getAllMetadata');
final db = await _database; final db = await _database;
final maps = await db.query(table); final maps = await db.query(metadataTable);
return maps.map((map) => CatalogMetadata.fromMap(map)).toList(); return maps.map((map) => CatalogMetadata.fromMap(map)).toList();
} }
Future<CatalogMetadata> get(int contentId) async { Future<CatalogMetadata> getMetadata(int contentId) async {
debugPrint('$runtimeType get contentId=$contentId'); debugPrint('$runtimeType getMetadata contentId=$contentId');
final db = await _database; final db = await _database;
List<Map> maps = await db.query(table, where: 'contentId = ?', whereArgs: [contentId]); List<Map> maps = await db.query(metadataTable, where: 'contentId = ?', whereArgs: [contentId]);
if (maps.length > 0) { if (maps.length > 0) {
return CatalogMetadata.fromMap(maps.first); return CatalogMetadata.fromMap(maps.first);
} }
return null; return null;
} }
insert(CatalogMetadata metadata) async { insertMetadata(CatalogMetadata metadata) async {
// debugPrint('$runtimeType insert metadata=$metadata'); // debugPrint('$runtimeType insertMetadata metadata=$metadata');
final db = await _database; final db = await _database;
await db.insert( await db.insert(
table, metadataTable,
metadata.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<List<AddressDetails>> getAllAddresses() async {
debugPrint('$runtimeType getAllAddresses');
final db = await _database;
final maps = await db.query(addressTable);
return maps.map((map) => AddressDetails.fromMap(map)).toList();
}
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;
}
insertAddress(AddressDetails metadata) async {
// debugPrint('$runtimeType insertAddress metadata=$metadata');
final db = await _database;
await db.insert(
addressTable,
metadata.toMap(), metadata.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace, conflictAlgorithm: ConflictAlgorithm.replace,
); );

View file

@ -35,7 +35,7 @@ class MetadataService {
}) as Map; }) as Map;
result['contentId'] = entry.contentId; result['contentId'] = entry.contentId;
metadata = CatalogMetadata.fromMap(result); metadata = CatalogMetadata.fromMap(result);
metadataDb.insert(metadata); metadataDb.insertMetadata(metadata);
return metadata; return metadata;
} on PlatformException catch (e) { } on PlatformException catch (e) {
debugPrint('getCatalogMetadata failed with exception=${e.message}'); debugPrint('getCatalogMetadata failed with exception=${e.message}');

View file

@ -34,7 +34,9 @@ class AllCollectionPage extends StatelessWidget {
return Navigator.push( return Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DebugPage(), builder: (context) => DebugPage(
entries: entries,
),
), ),
); );
} }

View file

@ -1,49 +1,69 @@
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/metadata_db.dart'; import 'package:aves/model/metadata_db.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class DebugPage extends StatefulWidget { class DebugPage extends StatefulWidget {
final List<ImageEntry> entries;
const DebugPage({this.entries});
@override @override
State<StatefulWidget> createState() => DebugPageState(); State<StatefulWidget> createState() => DebugPageState();
} }
class DebugPageState extends State<DebugPage> { class DebugPageState extends State<DebugPage> {
Future<List<CatalogMetadata>> _dbLoader; Future<List<CatalogMetadata>> _dbMetadataLoader;
Future<List<AddressDetails>> _dbAddressLoader;
List<ImageEntry> get entries => widget.entries;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_dbLoader = metadataDb.getAll(); _dbMetadataLoader = metadataDb.getAllMetadata();
_dbAddressLoader = metadataDb.getAllAddresses();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Map<String, List<ImageEntry>> 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( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Info'), title: Text('Info'),
), ),
body: Column( body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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( RaisedButton(
onPressed: () => metadataDb.reset(), onPressed: () => metadataDb.reset(),
child: Text('Reset DB'), child: Text('Reset DB'),
), ),
Expanded( FutureBuilder(
child: FutureBuilder( future: _dbMetadataLoader,
future: _dbLoader, builder: (futureContext, AsyncSnapshot<List<CatalogMetadata>> snapshot) {
builder: (futureContext, AsyncSnapshot<List<CatalogMetadata>> snapshot) { if (snapshot.hasError) return Text(snapshot.error);
if (snapshot.hasError) return Text(snapshot.error); if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
if (snapshot.connectionState != ConnectionState.done) return Text('DB metadata rows: ${snapshot.data.length}');
return Center( },
child: CircularProgressIndicator(), ),
); FutureBuilder(
final metadata = snapshot.data; future: _dbAddressLoader,
return ListView.builder( builder: (futureContext, AsyncSnapshot<List<AddressDetails>> snapshot) {
itemBuilder: (context, index) => Text(' $index: ${metadata[index]}'), if (snapshot.hasError) return Text(snapshot.error);
itemCount: metadata.length, if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
); return Text('DB address rows: ${snapshot.data.length}');
}, },
),
), ),
], ],
), ),

View file

@ -20,7 +20,7 @@ class LocationSection extends AnimatedWidget {
if (entry.isLocated) if (entry.isLocated)
Padding( Padding(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
child: InfoRow('Address', entry.addressLine), child: InfoRow('Address', entry.addressDetails.addressLine),
), ),
], ],
); );

View file

@ -118,6 +118,24 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
overflow: TextOverflow.ellipsis, 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(height: 4),
SizedBox( SizedBox(
width: subRowWidth, width: subRowWidth,