fullscreen: added address to overlay
This commit is contained in:
parent
05af913d86
commit
e4da59a624
8 changed files with 160 additions and 40 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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}');
|
||||
|
|
|
@ -34,7 +34,9 @@ class AllCollectionPage extends StatelessWidget {
|
|||
return Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DebugPage(),
|
||||
builder: (context) => DebugPage(
|
||||
entries: entries,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
FutureBuilder(
|
||||
future: _dbMetadataLoader,
|
||||
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,
|
||||
);
|
||||
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}');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue