fixed album/country/tag background image and count update on source change

This commit is contained in:
Thibault Deckers 2021-02-25 19:17:56 +09:00
parent c86534d600
commit c7aef79f37
8 changed files with 97 additions and 31 deletions

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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,

View file

@ -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,
),
),
),
],
);
}

View file

@ -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,

View file

@ -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',

View file

@ -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, '');