filters: flag for country filter

This commit is contained in:
Thibault Deckers 2020-04-12 17:29:31 +09:00
parent 28d2dff8b5
commit 9c9c55e8cd
10 changed files with 76 additions and 31 deletions

View file

@ -124,7 +124,7 @@ class CollectionSource {
void updateLocations() {
final locations = _rawEntries.where((entry) => entry.isLocated).map((entry) => entry.addressDetails);
final lister = (String Function(AddressDetails a) f) => List<String>.unmodifiable(locations.map(f).where((s) => s != null && s.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase));
sortedCountries = lister((address) => address.countryName);
sortedCountries = lister((address) => '${address.countryName};${address.countryCode}');
sortedCities = lister((address) => address.city);
}

View file

@ -7,18 +7,27 @@ class LocationFilter extends CollectionFilter {
static const type = 'country';
final LocationLevel level;
final String location;
String _location;
String _countryCode;
const LocationFilter(this.level, this.location);
LocationFilter(this.level, this._location) {
final split = _location.split(';');
if (split.isNotEmpty) _location = split[0];
if (split.length > 1) _countryCode = split[1];
}
@override
bool filter(ImageEntry entry) => entry.isLocated && ((level == LocationLevel.country && entry.addressDetails.countryName == location) || (level == LocationLevel.city && entry.addressDetails.city == location));
bool filter(ImageEntry entry) => entry.isLocated && ((level == LocationLevel.country && entry.addressDetails.countryName == _location) || (level == LocationLevel.city && entry.addressDetails.city == _location));
@override
String get label => location;
String get label => _location;
@override
Widget iconBuilder(context, size) => Icon(OMIcons.place, size: size);
Widget iconBuilder(context, size) {
final flag = countryCodeToFlag(_countryCode);
if (flag != null) return Text(flag, style: TextStyle(fontSize: size));
return Icon(OMIcons.place, size: size);
}
@override
String get typeKey => type;
@ -26,11 +35,19 @@ class LocationFilter extends CollectionFilter {
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is LocationFilter && other.location == location;
return other is LocationFilter && other._location == _location;
}
@override
int get hashCode => hashValues('LocationFilter', location);
int get hashCode => hashValues('LocationFilter', _location);
// U+0041 Latin Capital letter A
// U+1F1E6 🇦 REGIONAL INDICATOR SYMBOL LETTER A
static const _countryCodeToFlagDiff = 0x1F1E6 - 0x0041;
static String countryCodeToFlag(String code) {
return code?.length == 2 ? String.fromCharCodes(code.codeUnits.map((letter) => letter += _countryCodeToFlagDiff)) : null;
}
}
enum LocationLevel { city, country }

View file

@ -223,6 +223,7 @@ class ImageEntry {
addressDetails = AddressDetails(
contentId: contentId,
addressLine: address.addressLine,
countryCode: address.countryCode,
countryName: address.countryName,
adminArea: address.adminArea,
locality: address.locality,

View file

@ -103,13 +103,14 @@ class OverlayMetadata {
class AddressDetails {
final int contentId;
final String addressLine, countryName, adminArea, locality;
final String addressLine, countryCode, countryName, adminArea, locality;
String get city => locality != null && locality.isNotEmpty ? locality : adminArea;
AddressDetails({
this.contentId,
this.addressLine,
this.countryCode,
this.countryName,
this.adminArea,
this.locality,
@ -119,6 +120,7 @@ class AddressDetails {
return AddressDetails(
contentId: map['contentId'],
addressLine: map['addressLine'] ?? '',
countryCode: map['countryCode'] ?? '',
countryName: map['countryName'] ?? '',
adminArea: map['adminArea'] ?? '',
locality: map['locality'] ?? '',
@ -128,6 +130,7 @@ class AddressDetails {
Map<String, dynamic> toMap() => {
'contentId': contentId,
'addressLine': addressLine,
'countryCode': countryCode,
'countryName': countryName,
'adminArea': adminArea,
'locality': locality,
@ -135,7 +138,7 @@ class AddressDetails {
@override
String toString() {
return 'AddressDetails{contentId=$contentId, addressLine=$addressLine, countryName=$countryName, adminArea=$adminArea, locality=$locality}';
return 'AddressDetails{contentId=$contentId, addressLine=$addressLine, countryCode=$countryCode, countryName=$countryName, adminArea=$adminArea, locality=$locality}';
}
}

View file

@ -26,7 +26,7 @@ class MetadataDb {
onCreate: (db, version) async {
await db.execute('CREATE TABLE $dateTakenTable(contentId INTEGER PRIMARY KEY, dateMillis INTEGER)');
await db.execute('CREATE TABLE $metadataTable(contentId INTEGER PRIMARY KEY, dateMillis INTEGER, videoRotation INTEGER, xmpSubjects TEXT, xmpTitleDescription TEXT, latitude REAL, longitude REAL)');
await db.execute('CREATE TABLE $addressTable(contentId INTEGER PRIMARY KEY, addressLine TEXT, countryName TEXT, adminArea TEXT, locality TEXT)');
await db.execute('CREATE TABLE $addressTable(contentId INTEGER PRIMARY KEY, addressLine TEXT, countryCode TEXT, countryName TEXT, adminArea TEXT, locality TEXT)');
await db.execute('CREATE TABLE $favouriteTable(contentId INTEGER PRIMARY KEY, path TEXT)');
},
version: 1,

View file

@ -93,14 +93,14 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
title: 'Favourites',
filter: FavouriteFilter(),
);
final buildAlbumEntry = (album) => _FilteredCollectionNavTile(
final buildAlbumEntry = (String album) => _FilteredCollectionNavTile(
source: source,
leading: IconUtils.getAlbumIcon(context: context, album: album),
title: source.getUniqueAlbumName(album),
dense: true,
filter: AlbumFilter(album, source.getUniqueAlbumName(album)),
);
final buildTagEntry = (tag) => _FilteredCollectionNavTile(
final buildTagEntry = (String tag) => _FilteredCollectionNavTile(
source: source,
leading: Icon(
OMIcons.localOffer,
@ -110,18 +110,36 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
dense: true,
filter: TagFilter(tag),
);
final buildLocationEntry = (level, location) => _FilteredCollectionNavTile(
source: source,
leading: Icon(
OMIcons.place,
color: stringToColor(location),
),
title: location,
dense: true,
filter: LocationFilter(level, location),
);
final buildLocationEntry = (LocationLevel level, String location) {
String title;
String flag;
if (level == LocationLevel.country) {
final split = location.split(';');
String countryCode;
if (split.isNotEmpty) title = split[0];
if (split.length > 1) countryCode = split[1];
flag = LocationFilter.countryCodeToFlag(countryCode);
} else {
title = location;
}
return _FilteredCollectionNavTile(
source: source,
leading: flag != null
? Text(
flag,
style: TextStyle(fontSize: IconTheme.of(context).size),
)
: Icon(
OMIcons.place,
color: stringToColor(title),
),
title: title,
dense: true,
filter: LocationFilter(level, location),
);
};
final regularAlbums = [], appAlbums = [], specialAlbums = [];
final regularAlbums = <String>[], appAlbums = <String>[], specialAlbums = <String>[];
for (var album in source.sortedAlbums) {
switch (androidFileUtils.getAlbumType(album)) {
case AlbumType.Default:

View file

@ -4,8 +4,6 @@ import 'package:outline_material_icons/outline_material_icons.dart';
typedef FilterCallback = void Function(CollectionFilter filter);
typedef FilterBuilder = CollectionFilter Function(String label);
class AvesFilterChip extends StatefulWidget {
final CollectionFilter filter;
final bool removable;

View file

@ -92,6 +92,7 @@ class _FullscreenDebugPageState extends State<FullscreenDebugPage> {
if (data != null)
InfoRowGroup({
'dateMillis': '${data.addressLine}',
'countryCode': '${data.countryCode}',
'countryName': '${data.countryName}',
'adminArea': '${data.adminArea}',
'locality': '${data.locality}',

View file

@ -80,7 +80,7 @@ class _LocationSectionState extends State<LocationSection> {
final address = entry.addressDetails;
location = address.addressLine;
final country = address.countryName;
if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, country));
if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country;${address.countryCode}'));
final city = address.city;
if (city != null && city.isNotEmpty) filters.add(LocationFilter(LocationLevel.city, city));
} else if (entry.hasGps) {

View file

@ -34,8 +34,9 @@ class StatsPage extends StatelessWidget {
if (city != null && city.isNotEmpty) {
entryCountPerCity[city] = (entryCountPerCity[city] ?? 0) + 1;
}
final country = address.countryName;
var country = address.countryName;
if (country != null && country.isNotEmpty) {
country += ';${address.countryCode}';
entryCountPerCountry[country] = (entryCountPerCountry[country] ?? 0) + 1;
}
}
@ -193,7 +194,12 @@ class StatsPage extends StatelessWidget {
});
}
List<Widget> _buildTopFilters(BuildContext context, String title, Map<String, int> entryCountMap, FilterBuilder filterBuilder) {
List<Widget> _buildTopFilters(
BuildContext context,
String title,
Map<String, int> entryCountMap,
CollectionFilter Function(String key) filterBuilder,
) {
if (entryCountMap.isEmpty) return [];
final maxCount = collection.entryCount;
@ -214,7 +220,8 @@ class StatsPage extends StatelessWidget {
padding: const EdgeInsetsDirectional.only(start: AvesFilterChip.buttonBorderWidth / 2 + 6, end: 8),
child: Table(
children: sortedEntries.take(5).map((kv) {
final label = kv.key;
final filter = filterBuilder(kv.key);
final label = filter.label;
final count = kv.value;
final percent = count / maxCount;
return TableRow(
@ -222,7 +229,7 @@ class StatsPage extends StatelessWidget {
Align(
alignment: AlignmentDirectional.centerStart,
child: AvesFilterChip(
filter: filterBuilder(label),
filter: filter,
onPressed: (filter) => _goToCollection(context, filter),
),
),