city filter
This commit is contained in:
parent
94b8ddc854
commit
4ab75fe218
11 changed files with 109 additions and 67 deletions
|
@ -21,7 +21,7 @@ class CollectionLens with ChangeNotifier {
|
|||
|
||||
CollectionLens({
|
||||
@required this.source,
|
||||
List<CollectionFilter> filters,
|
||||
Iterable<CollectionFilter> filters,
|
||||
@required GroupFactor groupFactor,
|
||||
@required SortFactor sortFactor,
|
||||
}) : this.filters = [if (filters != null) ...filters.where((f) => f != null)].toSet(),
|
||||
|
@ -53,13 +53,10 @@ class CollectionLens with ChangeNotifier {
|
|||
CollectionLens derive(CollectionFilter filter) {
|
||||
return CollectionLens(
|
||||
source: source,
|
||||
filters: [
|
||||
...filters,
|
||||
filter,
|
||||
],
|
||||
filters: filters,
|
||||
groupFactor: groupFactor,
|
||||
sortFactor: sortFactor,
|
||||
);
|
||||
)..addFilter(filter);
|
||||
}
|
||||
|
||||
bool get isEmpty => _filteredEntries.isEmpty;
|
||||
|
|
|
@ -11,8 +11,9 @@ class CollectionSource {
|
|||
final EventBus _eventBus = EventBus();
|
||||
|
||||
List<String> sortedAlbums = List.unmodifiable(const Iterable.empty());
|
||||
List<String> sortedTags = List.unmodifiable(const Iterable.empty());
|
||||
List<String> sortedCities = List.unmodifiable(const Iterable.empty());
|
||||
List<String> sortedCountries = List.unmodifiable(const Iterable.empty());
|
||||
List<String> sortedTags = List.unmodifiable(const Iterable.empty());
|
||||
|
||||
List<ImageEntry> get entries => List.unmodifiable(_rawEntries);
|
||||
|
||||
|
@ -117,9 +118,10 @@ class CollectionSource {
|
|||
}
|
||||
|
||||
void updateLocations() {
|
||||
final locatedEntries = _rawEntries.where((entry) => entry.isLocated);
|
||||
final countries = locatedEntries.map((entry) => entry.addressDetails.countryName).toSet().toList()..sort(compareAsciiUpperCase);
|
||||
sortedCountries = List.unmodifiable(countries);
|
||||
final locations = _rawEntries.where((entry) => entry.isLocated).map((entry) => entry.addressDetails);
|
||||
final lister = (String f(AddressDetails a)) => List<String>.unmodifiable(locations.map(f).where((s) => s != null && s.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase));
|
||||
sortedCountries = lister((address) => address.countryName);
|
||||
sortedCities = lister((address) => address.city);
|
||||
}
|
||||
|
||||
void add(ImageEntry entry) {
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
|
||||
class CountryFilter extends CollectionFilter {
|
||||
static const type = 'country';
|
||||
|
||||
final String country;
|
||||
|
||||
const CountryFilter(this.country);
|
||||
|
||||
@override
|
||||
bool filter(ImageEntry entry) => entry.isLocated && entry.addressDetails.countryName == country;
|
||||
|
||||
@override
|
||||
String get label => country;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(context, size) => Icon(OMIcons.place, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is CountryFilter && other.country == country;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues('CountryFilter', country);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/country.dart';
|
||||
import 'package:aves/model/filters/favourite.dart';
|
||||
import 'package:aves/model/filters/gif.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/filters/query.dart';
|
||||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/filters/video.dart';
|
||||
|
@ -18,7 +18,7 @@ abstract class CollectionFilter implements Comparable<CollectionFilter> {
|
|||
VideoFilter.type,
|
||||
GifFilter.type,
|
||||
AlbumFilter.type,
|
||||
CountryFilter.type,
|
||||
LocationFilter.type,
|
||||
TagFilter.type,
|
||||
];
|
||||
|
||||
|
|
36
lib/model/filters/location.dart
Normal file
36
lib/model/filters/location.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
|
||||
class LocationFilter extends CollectionFilter {
|
||||
static const type = 'country';
|
||||
|
||||
final LocationLevel level;
|
||||
final String location;
|
||||
|
||||
const LocationFilter(this.level, this.location);
|
||||
|
||||
@override
|
||||
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;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(context, size) => Icon(OMIcons.place, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is LocationFilter && other.location == location;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues('LocationFilter', location);
|
||||
}
|
||||
|
||||
enum LocationLevel { city, country }
|
|
@ -76,6 +76,8 @@ class AddressDetails {
|
|||
final int contentId;
|
||||
final String addressLine, countryName, adminArea, locality;
|
||||
|
||||
String get city => locality != null && locality.isNotEmpty ? locality : adminArea;
|
||||
|
||||
AddressDetails({
|
||||
this.contentId,
|
||||
this.addressLine,
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:ui';
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/collection_source.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/country.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/filters/favourite.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/gif.dart';
|
||||
|
@ -31,7 +31,7 @@ class CollectionDrawer extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _CollectionDrawerState extends State<CollectionDrawer> {
|
||||
bool _albumsExpanded = false, _tagsExpanded = false, _countriesExpanded = false;
|
||||
bool _albumsExpanded = false, _citiesExpanded = false, _countriesExpanded = false, _tagsExpanded = false;
|
||||
|
||||
CollectionSource get source => widget.source;
|
||||
|
||||
|
@ -110,15 +110,15 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
|
|||
dense: true,
|
||||
filter: TagFilter(tag),
|
||||
);
|
||||
final buildCountryEntry = (country) => _FilteredCollectionNavTile(
|
||||
final buildLocationEntry = (level, location) => _FilteredCollectionNavTile(
|
||||
source: source,
|
||||
leading: Icon(
|
||||
OMIcons.place,
|
||||
color: stringToColor(country),
|
||||
color: stringToColor(location),
|
||||
),
|
||||
title: country,
|
||||
title: location,
|
||||
dense: true,
|
||||
filter: CountryFilter(country),
|
||||
filter: LocationFilter(level, location),
|
||||
);
|
||||
|
||||
final regularAlbums = [], appAlbums = [], specialAlbums = [];
|
||||
|
@ -135,6 +135,7 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
|
|||
break;
|
||||
}
|
||||
}
|
||||
final cities = source.sortedCities;
|
||||
final countries = source.sortedCountries;
|
||||
final tags = source.sortedTags;
|
||||
|
||||
|
@ -174,6 +175,28 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (cities.isNotEmpty)
|
||||
SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: ExpansionTile(
|
||||
leading: const Icon(OMIcons.place),
|
||||
title: Row(
|
||||
children: [
|
||||
const Text('Cities'),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'${cities.length}',
|
||||
style: TextStyle(
|
||||
color: (_citiesExpanded ? Theme.of(context).accentColor : Colors.white).withOpacity(.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onExpansionChanged: (expanded) => setState(() => _citiesExpanded = expanded),
|
||||
children: cities.map((s) => buildLocationEntry(LocationLevel.city, s)).toList(),
|
||||
),
|
||||
),
|
||||
if (countries.isNotEmpty)
|
||||
SafeArea(
|
||||
top: false,
|
||||
|
@ -193,7 +216,7 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
|
|||
],
|
||||
),
|
||||
onExpansionChanged: (expanded) => setState(() => _countriesExpanded = expanded),
|
||||
children: countries.map(buildCountryEntry).toList(),
|
||||
children: countries.map((s) => buildLocationEntry(LocationLevel.country, s)).toList(),
|
||||
),
|
||||
),
|
||||
if (tags.isNotEmpty)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/collection_source.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/country.dart';
|
||||
import 'package:aves/model/filters/favourite.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/gif.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/filters/query.dart';
|
||||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/filters/video.dart';
|
||||
|
@ -71,10 +71,15 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
|
|||
title: 'Albums',
|
||||
filters: source.sortedAlbums.where(containQuery).map((s) => AlbumFilter(s, CollectionSource.getUniqueAlbumName(s, source.sortedAlbums))).where((f) => containQuery(f.uniqueName)),
|
||||
),
|
||||
_buildFilterRow(
|
||||
context: context,
|
||||
title: 'Cities',
|
||||
filters: source.sortedCities.where(containQuery).map((s) => LocationFilter(LocationLevel.city, s)),
|
||||
),
|
||||
_buildFilterRow(
|
||||
context: context,
|
||||
title: 'Countries',
|
||||
filters: source.sortedCountries.where(containQuery).map((s) => CountryFilter(s)),
|
||||
filters: source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)),
|
||||
),
|
||||
_buildFilterRow(
|
||||
context: context,
|
||||
|
|
|
@ -58,7 +58,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
children: [
|
||||
if (leading != null) ...[
|
||||
leading,
|
||||
const SizedBox(width: AvesFilterChip.padding * 1.6),
|
||||
const SizedBox(width: AvesFilterChip.padding),
|
||||
],
|
||||
Flexible(
|
||||
child: Text(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/filters/country.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/utils/android_app_service.dart';
|
||||
|
@ -74,15 +74,17 @@ class _LocationSectionState extends State<LocationSection> {
|
|||
if (showMap) {
|
||||
_loadedUri = entry.uri;
|
||||
String location = '';
|
||||
final List<LocationFilter> filters = [];
|
||||
if (entry.isLocated) {
|
||||
location = entry.addressDetails.addressLine;
|
||||
final address = entry.addressDetails;
|
||||
location = address.addressLine;
|
||||
final country = address.countryName;
|
||||
if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, country));
|
||||
final city = address.city;
|
||||
if (city != null && city.isNotEmpty) filters.add(LocationFilter(LocationLevel.city, city));
|
||||
} else if (entry.hasGps) {
|
||||
location = toDMS(entry.latLng).join(', ');
|
||||
}
|
||||
final country = entry.addressDetails?.countryName;
|
||||
final filters = [
|
||||
if (country != null) CountryFilter(country),
|
||||
];
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/filters/country.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/color_utils.dart';
|
||||
|
@ -17,14 +17,21 @@ import 'package:percent_indicator/linear_percent_indicator.dart';
|
|||
|
||||
class StatsPage extends StatelessWidget {
|
||||
final CollectionLens collection;
|
||||
final Map<String, int> entryCountPerCountry = Map<String, int>(), entryCountPerTag = Map<String, int>();
|
||||
final Map<String, int> entryCountPerCity = Map(), entryCountPerCountry = Map(), entryCountPerTag = Map();
|
||||
|
||||
StatsPage({this.collection}) {
|
||||
entries.forEach((entry) {
|
||||
final country = entry.addressDetails?.countryName;
|
||||
if (country != null) {
|
||||
if (entry.isLocated) {
|
||||
final address = entry.addressDetails;
|
||||
final city = address.city;
|
||||
if (city != null && city.isNotEmpty) {
|
||||
entryCountPerCity[city] = (entryCountPerCity[city] ?? 0) + 1;
|
||||
}
|
||||
final country = address.countryName;
|
||||
if (country != null && country.isNotEmpty) {
|
||||
entryCountPerCountry[country] = (entryCountPerCountry[country] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
entry.xmpSubjects.forEach((tag) {
|
||||
entryCountPerTag[tag] = (entryCountPerTag[tag] ?? 0) + 1;
|
||||
});
|
||||
|
@ -76,7 +83,8 @@ class StatsPage extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
..._buildTopFilters(context, 'Top countries', entryCountPerCountry, (s) => CountryFilter(s)),
|
||||
..._buildTopFilters(context, 'Top cities', entryCountPerCity, (s) => LocationFilter(LocationLevel.city, s)),
|
||||
..._buildTopFilters(context, 'Top countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)),
|
||||
..._buildTopFilters(context, 'Top tags', entryCountPerTag, (s) => TagFilter(s)),
|
||||
],
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue