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/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<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);
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;
}
}

View file

@ -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<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');
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<List<CatalogMetadata>> getAll() async {
debugPrint('$runtimeType getAll');
Future<List<CatalogMetadata>> 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<CatalogMetadata> get(int contentId) async {
debugPrint('$runtimeType get contentId=$contentId');
Future<CatalogMetadata> getMetadata(int contentId) async {
debugPrint('$runtimeType getMetadata contentId=$contentId');
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) {
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<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(),
conflictAlgorithm: ConflictAlgorithm.replace,
);

View file

@ -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}');

View file

@ -34,7 +34,9 @@ class AllCollectionPage extends StatelessWidget {
return Navigator.push(
context,
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/metadata_db.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
class DebugPage extends StatefulWidget {
final List<ImageEntry> entries;
const DebugPage({this.entries});
@override
State<StatefulWidget> createState() => DebugPageState();
}
class DebugPageState extends State<DebugPage> {
Future<List<CatalogMetadata>> _dbLoader;
Future<List<CatalogMetadata>> _dbMetadataLoader;
Future<List<AddressDetails>> _dbAddressLoader;
List<ImageEntry> 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<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(
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<List<CatalogMetadata>> 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<List<CatalogMetadata>> 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<List<AddressDetails>> snapshot) {
if (snapshot.hasError) return Text(snapshot.error);
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)
Padding(
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,
),
),
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,