diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index e9a3cc57e..431f52e18 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -20,11 +20,12 @@ class AlbumFilter extends CoveredCollectionFilter { const AlbumFilter(this.album, this.displayName); - AlbumFilter.fromMap(Map json) - : this( - json['album'], - json['uniqueName'], - ); + factory AlbumFilter.fromMap(Map json) { + return AlbumFilter( + json['album'], + json['uniqueName'], + ); + } @override Map toMap() => { diff --git a/lib/model/filters/coordinate.dart b/lib/model/filters/coordinate.dart index 5b42c7e41..f08edc62e 100644 --- a/lib/model/filters/coordinate.dart +++ b/lib/model/filters/coordinate.dart @@ -23,11 +23,12 @@ class CoordinateFilter extends CollectionFilter { const CoordinateFilter(this.sw, this.ne, {this.minuteSecondPadding = false}); - CoordinateFilter.fromMap(Map json) - : this( - LatLng.fromJson(json['sw']), - LatLng.fromJson(json['ne']), - ); + factory CoordinateFilter.fromMap(Map json) { + return CoordinateFilter( + LatLng.fromJson(json['sw']), + LatLng.fromJson(json['ne']), + ); + } @override Map toMap() => { diff --git a/lib/model/filters/date.dart b/lib/model/filters/date.dart new file mode 100644 index 000000000..17a94a947 --- /dev/null +++ b/lib/model/filters/date.dart @@ -0,0 +1,91 @@ +import 'package:aves/model/device.dart'; +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; + +class LocationFilter extends CoveredCollectionFilter { + static const type = 'location'; + static const locationSeparator = ';'; + + final LocationLevel level; + late final String _location; + late final String? _countryCode; + late final EntryFilter _test; + + @override + List get props => [level, _location, _countryCode]; + + LocationFilter(this.level, String location) { + final split = location.split(locationSeparator); + _location = split.isNotEmpty ? split[0] : location; + _countryCode = split.length > 1 ? split[1] : null; + + if (_location.isEmpty) { + _test = (entry) => !entry.hasGps; + } else if (level == LocationLevel.country) { + _test = (entry) => entry.addressDetails?.countryCode == _countryCode; + } else if (level == LocationLevel.place) { + _test = (entry) => entry.addressDetails?.place == _location; + } + } + + LocationFilter.fromMap(Map json) + : this( + LocationLevel.values.firstWhereOrNull((v) => v.toString() == json['level']) ?? LocationLevel.place, + json['location'], + ); + + @override + Map toMap() => { + 'type': type, + 'level': level.toString(), + 'location': _countryCode != null ? countryNameAndCode : _location, + }; + + String get countryNameAndCode => '$_location$locationSeparator$_countryCode'; + + String? get countryCode => _countryCode; + + @override + EntryFilter get test => _test; + + @override + String get universalLabel => _location; + + @override + String getLabel(BuildContext context) => _location.isEmpty ? context.l10n.filterLocationEmptyLabel : _location; + + @override + Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) { + if (_countryCode != null && device.canRenderFlagEmojis) { + final flag = countryCodeToFlag(_countryCode); + if (flag != null) { + return Text( + flag, + style: TextStyle(fontSize: size), + textScaleFactor: 1.0, + ); + } + } + return Icon(_location.isEmpty ? AIcons.locationUnlocated : AIcons.location, size: size); + } + + @override + String get category => type; + + @override + String get key => '$type-$level-$_location'; + + // U+0041 Latin Capital letter A + // U+1F1E6 🇦 REGIONAL INDICATOR SYMBOL LETTER A + static const _countryCodeToFlagDiff = 0x1F1E6 - 0x0041; + + static String? countryCodeToFlag(String? code) { + if (code == null || code.length != 2) return null; + return String.fromCharCodes(code.toUpperCase().codeUnits.map((letter) => letter += _countryCodeToFlagDiff)); + } +} + +enum LocationLevel { place, country } diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart index 17a94a947..8b3bf1098 100644 --- a/lib/model/filters/location.dart +++ b/lib/model/filters/location.dart @@ -31,11 +31,12 @@ class LocationFilter extends CoveredCollectionFilter { } } - LocationFilter.fromMap(Map json) - : this( - LocationLevel.values.firstWhereOrNull((v) => v.toString() == json['level']) ?? LocationLevel.place, - json['location'], - ); + factory LocationFilter.fromMap(Map json) { + return LocationFilter( + LocationLevel.values.firstWhereOrNull((v) => v.toString() == json['level']) ?? LocationLevel.place, + json['location'], + ); + } @override Map toMap() => { diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart index 51338cb6f..98fb3f9de 100644 --- a/lib/model/filters/mime.dart +++ b/lib/model/filters/mime.dart @@ -43,10 +43,11 @@ class MimeFilter extends CollectionFilter { _icon = icon ?? AIcons.vector; } - MimeFilter.fromMap(Map json) - : this( - json['mime'], - ); + factory MimeFilter.fromMap(Map json) { + return MimeFilter( + json['mime'], + ); + } @override Map toMap() => { diff --git a/lib/model/filters/path.dart b/lib/model/filters/path.dart index 406b7de89..ce1c1ead8 100644 --- a/lib/model/filters/path.dart +++ b/lib/model/filters/path.dart @@ -15,10 +15,11 @@ class PathFilter extends CollectionFilter { PathFilter(this.path) : _rootAlbum = path.substring(0, path.length - 1); - PathFilter.fromMap(Map json) - : this( - json['path'], - ); + factory PathFilter.fromMap(Map json) { + return PathFilter( + json['path'], + ); + } @override Map toMap() => { diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index 8d73336fd..8e6eb7875 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -59,10 +59,11 @@ class QueryFilter extends CollectionFilter { _test = not ? (entry) => !testTitle(entry) : testTitle; } - QueryFilter.fromMap(Map json) - : this( - json['query'], - ); + factory QueryFilter.fromMap(Map json) { + return QueryFilter( + json['query'], + ); + } @override Map toMap() => { diff --git a/lib/model/filters/rating.dart b/lib/model/filters/rating.dart index 495faebe4..7eadd4e8c 100644 --- a/lib/model/filters/rating.dart +++ b/lib/model/filters/rating.dart @@ -13,10 +13,11 @@ class RatingFilter extends CollectionFilter { const RatingFilter(this.rating); - RatingFilter.fromMap(Map json) - : this( - json['rating'] ?? 0, - ); + factory RatingFilter.fromMap(Map json) { + return RatingFilter( + json['rating'] ?? 0, + ); + } @override Map toMap() => { diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index 750e420cb..aa9b09453 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -20,11 +20,12 @@ class TagFilter extends CoveredCollectionFilter { } } - TagFilter.fromMap(Map json) - : this( - json['tag'], - not: json['not'] ?? false, - ); + factory TagFilter.fromMap(Map json) { + return TagFilter( + json['tag'], + not: json['not'] ?? false, + ); + } @override Map toMap() => { diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart index 300f6bc19..548163004 100644 --- a/lib/model/filters/type.dart +++ b/lib/model/filters/type.dart @@ -59,10 +59,11 @@ class TypeFilter extends CollectionFilter { } } - TypeFilter.fromMap(Map json) - : this._private( - json['itemType'], - ); + factory TypeFilter.fromMap(Map json) { + return TypeFilter._private( + json['itemType'], + ); + } @override Map toMap() => { diff --git a/lib/widgets/stats/histogram.dart b/lib/widgets/stats/histogram.dart new file mode 100644 index 000000000..e69de29bb