fixed album/country/tag background image and count update on source change
This commit is contained in:
parent
c86534d600
commit
c7aef79f37
8 changed files with 97 additions and 31 deletions
|
@ -121,6 +121,7 @@ mixin AlbumMixin on SourceBase {
|
|||
directories.forEach(_filterEntryCountMap.remove);
|
||||
directories.forEach(_filterRecentEntryMap.remove);
|
||||
}
|
||||
eventBus.fire(AlbumSummaryInvalidatedEvent(directories));
|
||||
}
|
||||
|
||||
int albumEntryCount(AlbumFilter filter) {
|
||||
|
@ -133,3 +134,9 @@ mixin AlbumMixin on SourceBase {
|
|||
}
|
||||
|
||||
class AlbumsChangedEvent {}
|
||||
|
||||
class AlbumSummaryInvalidatedEvent {
|
||||
final Set<String> directories;
|
||||
|
||||
const AlbumSummaryInvalidatedEvent(this.directories);
|
||||
}
|
||||
|
|
|
@ -131,16 +131,22 @@ mixin LocationMixin on SourceBase {
|
|||
|
||||
void updateLocations() {
|
||||
final locations = visibleEntries.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails).toList();
|
||||
sortedPlaces = List<String>.unmodifiable(locations.map((address) => address.place).where((s) => s != null && s.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase));
|
||||
final updatedPlaces = locations.map((address) => address.place).where((s) => s != null && s.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase);
|
||||
if (!listEquals(updatedPlaces, sortedPlaces)) {
|
||||
sortedPlaces = List.unmodifiable(updatedPlaces);
|
||||
eventBus.fire(PlacesChangedEvent());
|
||||
}
|
||||
|
||||
// the same country code could be found with different country names
|
||||
// e.g. if the locale changed between geolocating calls
|
||||
// so we merge countries by code, keeping only one name for each code
|
||||
final countriesByCode = Map.fromEntries(locations.map((address) => MapEntry(address.countryCode, address.countryName)).where((kv) => kv.key != null && kv.key.isNotEmpty));
|
||||
sortedCountries = List<String>.unmodifiable(countriesByCode.entries.map((kv) => '${kv.value}${LocationFilter.locationSeparator}${kv.key}').toList()..sort(compareAsciiUpperCase));
|
||||
|
||||
final updatedCountries = countriesByCode.entries.map((kv) => '${kv.value}${LocationFilter.locationSeparator}${kv.key}').toList()..sort(compareAsciiUpperCase);
|
||||
if (!listEquals(updatedCountries, sortedCountries)) {
|
||||
sortedCountries = List.unmodifiable(updatedCountries);
|
||||
invalidateCountryFilterSummary();
|
||||
eventBus.fire(LocationsChangedEvent());
|
||||
eventBus.fire(CountriesChangedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
// filter summary
|
||||
|
@ -150,14 +156,16 @@ mixin LocationMixin on SourceBase {
|
|||
final Map<String, AvesEntry> _filterRecentEntryMap = {};
|
||||
|
||||
void invalidateCountryFilterSummary([Set<AvesEntry> entries]) {
|
||||
Set<String> countryCodes;
|
||||
if (entries == null) {
|
||||
_filterEntryCountMap.clear();
|
||||
_filterRecentEntryMap.clear();
|
||||
} else {
|
||||
final countryCodes = entries.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails.countryCode).toSet();
|
||||
countryCodes = entries.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails.countryCode).toSet();
|
||||
countryCodes.remove(null);
|
||||
countryCodes.forEach(_filterEntryCountMap.remove);
|
||||
}
|
||||
eventBus.fire(CountrySummaryInvalidatedEvent(countryCodes));
|
||||
}
|
||||
|
||||
int countryEntryCount(LocationFilter filter) {
|
||||
|
@ -171,4 +179,12 @@ mixin LocationMixin on SourceBase {
|
|||
|
||||
class AddressMetadataChangedEvent {}
|
||||
|
||||
class LocationsChangedEvent {}
|
||||
class PlacesChangedEvent {}
|
||||
|
||||
class CountriesChangedEvent {}
|
||||
|
||||
class CountrySummaryInvalidatedEvent {
|
||||
final Set<String> countryCodes;
|
||||
|
||||
const CountrySummaryInvalidatedEvent(this.countryCodes);
|
||||
}
|
||||
|
|
|
@ -56,12 +56,13 @@ mixin TagMixin on SourceBase {
|
|||
}
|
||||
|
||||
void updateTags() {
|
||||
final tags = visibleEntries.expand((entry) => entry.xmpSubjects).toSet().toList()..sort(compareAsciiUpperCase);
|
||||
sortedTags = List.unmodifiable(tags);
|
||||
|
||||
final updatedTags = visibleEntries.expand((entry) => entry.xmpSubjects).toSet().toList()..sort(compareAsciiUpperCase);
|
||||
if (!listEquals(updatedTags, sortedTags)) {
|
||||
sortedTags = List.unmodifiable(updatedTags);
|
||||
invalidateTagFilterSummary();
|
||||
eventBus.fire(TagsChangedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
// filter summary
|
||||
|
||||
|
@ -70,13 +71,15 @@ mixin TagMixin on SourceBase {
|
|||
final Map<String, AvesEntry> _filterRecentEntryMap = {};
|
||||
|
||||
void invalidateTagFilterSummary([Set<AvesEntry> entries]) {
|
||||
Set<String> tags;
|
||||
if (entries == null) {
|
||||
_filterEntryCountMap.clear();
|
||||
_filterRecentEntryMap.clear();
|
||||
} else {
|
||||
final tags = entries.where((entry) => entry.isCatalogued).expand((entry) => entry.xmpSubjects).toSet();
|
||||
tags = entries.where((entry) => entry.isCatalogued).expand((entry) => entry.xmpSubjects).toSet();
|
||||
tags.forEach(_filterEntryCountMap.remove);
|
||||
}
|
||||
eventBus.fire(TagSummaryInvalidatedEvent(tags));
|
||||
}
|
||||
|
||||
int tagEntryCount(TagFilter filter) {
|
||||
|
@ -91,3 +94,9 @@ mixin TagMixin on SourceBase {
|
|||
class CatalogMetadataChangedEvent {}
|
||||
|
||||
class TagsChangedEvent {}
|
||||
|
||||
class TagSummaryInvalidatedEvent {
|
||||
final Set<String> tags;
|
||||
|
||||
const TagSummaryInvalidatedEvent(this.tags);
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||
icon: AIcons.location,
|
||||
title: 'Countries',
|
||||
trailing: StreamBuilder(
|
||||
stream: source.eventBus.on<LocationsChangedEvent>(),
|
||||
stream: source.eventBus.on<CountriesChangedEvent>(),
|
||||
builder: (context, _) => Text('${source.sortedCountries.length}'),
|
||||
),
|
||||
routeName: CountryListPage.routeName,
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/album.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/source/album.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/location.dart';
|
||||
import 'package:aves/model/source/tag.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
|
@ -20,7 +24,6 @@ import 'package:provider/provider.dart';
|
|||
|
||||
class DecoratedFilterChip extends StatelessWidget {
|
||||
final CollectionFilter filter;
|
||||
final AvesEntry entry;
|
||||
final double extent;
|
||||
final bool pinned, highlightable;
|
||||
final FilterCallback onTap;
|
||||
|
@ -29,7 +32,6 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
const DecoratedFilterChip({
|
||||
Key key,
|
||||
@required this.filter,
|
||||
@required this.entry,
|
||||
@required this.extent,
|
||||
this.pinned = false,
|
||||
this.highlightable = true,
|
||||
|
@ -39,6 +41,42 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<CollectionSource>(
|
||||
builder: (context, source, child) {
|
||||
switch (filter.runtimeType) {
|
||||
case AlbumFilter:
|
||||
{
|
||||
final album = (filter as AlbumFilter).album;
|
||||
return StreamBuilder<AlbumSummaryInvalidatedEvent>(
|
||||
stream: source.eventBus.on<AlbumSummaryInvalidatedEvent>().where((event) => event.directories == null || event.directories.contains(album)),
|
||||
builder: (context, snapshot) => _buildChip(source),
|
||||
);
|
||||
}
|
||||
case LocationFilter:
|
||||
{
|
||||
final countryCode = (filter as LocationFilter).countryCode;
|
||||
return StreamBuilder<CountrySummaryInvalidatedEvent>(
|
||||
stream: source.eventBus.on<CountrySummaryInvalidatedEvent>().where((event) => event.countryCodes == null || event.countryCodes.contains(countryCode)),
|
||||
builder: (context, snapshot) => _buildChip(source),
|
||||
);
|
||||
}
|
||||
case TagFilter:
|
||||
{
|
||||
final tag = (filter as TagFilter).tag;
|
||||
return StreamBuilder<TagSummaryInvalidatedEvent>(
|
||||
stream: source.eventBus.on<TagSummaryInvalidatedEvent>().where((event) => event.tags == null || event.tags.contains(tag)),
|
||||
builder: (context, snapshot) => _buildChip(source),
|
||||
);
|
||||
}
|
||||
default:
|
||||
return SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChip(CollectionSource source) {
|
||||
final entry = source.recentEntry(filter);
|
||||
final backgroundImage = entry == null
|
||||
? Container(color: Colors.white)
|
||||
: entry.isSvg
|
||||
|
@ -57,7 +95,7 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
filter: filter,
|
||||
showGenericIcon: false,
|
||||
background: backgroundImage,
|
||||
details: _buildDetails(filter),
|
||||
details: _buildDetails(source, filter),
|
||||
borderRadius: borderRadius,
|
||||
padding: titlePadding,
|
||||
onTap: onTap,
|
||||
|
@ -86,7 +124,7 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
return child;
|
||||
}
|
||||
|
||||
Widget _buildDetails(CollectionFilter filter) {
|
||||
Widget _buildDetails(CollectionSource source, CollectionFilter filter) {
|
||||
final padding = min<double>(8.0, extent / 16);
|
||||
final iconSize = min<double>(14.0, extent / 8);
|
||||
final fontSize = min<double>(14.0, extent / 6);
|
||||
|
@ -115,15 +153,13 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
size: iconSize,
|
||||
),
|
||||
),
|
||||
Consumer<CollectionSource>(
|
||||
builder: (context, source, child) => Text(
|
||||
Text(
|
||||
'${source.count(filter)}',
|
||||
style: TextStyle(
|
||||
color: FilterGridPage.detailColor,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -117,7 +117,6 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
|||
child: DecoratedFilterChip(
|
||||
key: Key(filter.key),
|
||||
filter: filter,
|
||||
entry: entry,
|
||||
extent: _tileExtentNotifier.value,
|
||||
pinned: pinnedFilters.contains(filter),
|
||||
onTap: onTap,
|
||||
|
@ -252,7 +251,6 @@ class _SectionedContentState<T extends CollectionFilter> extends State<_Sectione
|
|||
final filter = item.filter;
|
||||
return DecoratedFilterChip(
|
||||
filter: filter,
|
||||
entry: item.entry,
|
||||
extent: extent,
|
||||
pinned: pinnedFilters.contains(filter),
|
||||
highlightable: false,
|
||||
|
|
|
@ -26,7 +26,7 @@ class CountryListPage extends StatelessWidget {
|
|||
selector: (context, s) => Tuple2(s.countrySortFactor, s.pinnedFilters),
|
||||
builder: (context, s, child) {
|
||||
return StreamBuilder(
|
||||
stream: source.eventBus.on<LocationsChangedEvent>(),
|
||||
stream: source.eventBus.on<CountriesChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage<LocationFilter>(
|
||||
source: source,
|
||||
title: 'Countries',
|
||||
|
|
|
@ -110,7 +110,7 @@ class CollectionSearchDelegate {
|
|||
);
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<LocationsChangedEvent>(),
|
||||
stream: source.eventBus.on<CountriesChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)).toList();
|
||||
return _buildFilterRow(
|
||||
|
@ -120,7 +120,7 @@ class CollectionSearchDelegate {
|
|||
);
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<LocationsChangedEvent>(),
|
||||
stream: source.eventBus.on<PlacesChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s));
|
||||
final noFilter = LocationFilter(LocationLevel.place, '');
|
||||
|
|
Loading…
Reference in a new issue