filters: flag for country filter
This commit is contained in:
parent
28d2dff8b5
commit
9c9c55e8cd
10 changed files with 76 additions and 31 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
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: Icon(
|
||||
leading: flag != null
|
||||
? Text(
|
||||
flag,
|
||||
style: TextStyle(fontSize: IconTheme.of(context).size),
|
||||
)
|
||||
: Icon(
|
||||
OMIcons.place,
|
||||
color: stringToColor(location),
|
||||
color: stringToColor(title),
|
||||
),
|
||||
title: location,
|
||||
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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue