#170 view: load dir entries only, prevent navigation from filters, do not group bursts, do not listen to source

This commit is contained in:
Thibault Deckers 2022-02-21 21:37:12 +09:00
parent 6b4d9c0bc3
commit b1bf026ffd
27 changed files with 273 additions and 162 deletions

View file

@ -20,11 +20,13 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
private lateinit var handler: Handler private lateinit var handler: Handler
private var knownEntries: Map<Int?, Int?>? = null private var knownEntries: Map<Int?, Int?>? = null
private var directory: String? = null
init { init {
if (arguments is Map<*, *>) { if (arguments is Map<*, *>) {
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
knownEntries = arguments["knownEntries"] as Map<Int?, Int?>? knownEntries = arguments["knownEntries"] as Map<Int?, Int?>?
directory = arguments["directory"] as String?
} }
} }
@ -58,7 +60,7 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
} }
private fun fetchAll() { private fun fetchAll() {
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap()) { success(it) } MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap(), directory) { success(it) }
endOfStream() endOfStream()
} }

View file

@ -37,13 +37,24 @@ import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
class MediaStoreImageProvider : ImageProvider() { class MediaStoreImageProvider : ImageProvider() {
fun fetchAll(context: Context, knownEntries: Map<Int?, Int?>, handleNewEntry: NewEntryHandler) { fun fetchAll(
context: Context,
knownEntries: Map<Int?, Int?>,
directory: String?,
handleNewEntry: NewEntryHandler,
) {
val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean { val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean {
val knownDate = knownEntries[contentId] val knownDate = knownEntries[contentId]
return knownDate == null || knownDate < dateModifiedSecs return knownDate == null || knownDate < dateModifiedSecs
} }
fetchFrom(context, isModified, handleNewEntry, IMAGE_CONTENT_URI, IMAGE_PROJECTION) var selection: String? = null
fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION) var selectionArgs: Array<String>? = null
if (directory != null) {
selection = "${MediaColumns.PATH} LIKE ?"
selectionArgs = arrayOf("${StorageUtils.ensureTrailingSeparator(directory)}%")
}
fetchFrom(context, isModified, handleNewEntry, IMAGE_CONTENT_URI, IMAGE_PROJECTION, selection = selection, selectionArgs = selectionArgs)
fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION, selection = selection, selectionArgs = selectionArgs)
} }
// the provided URI can point to the wrong media collection, // the provided URI can point to the wrong media collection,
@ -138,12 +149,14 @@ class MediaStoreImageProvider : ImageProvider() {
handleNewEntry: NewEntryHandler, handleNewEntry: NewEntryHandler,
contentUri: Uri, contentUri: Uri,
projection: Array<String>, projection: Array<String>,
selection: String? = null,
selectionArgs: Array<String>? = null,
fileMimeType: String? = null, fileMimeType: String? = null,
): Boolean { ): Boolean {
var found = false var found = false
val orderBy = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC" val orderBy = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
try { try {
val cursor = context.contentResolver.query(contentUri, projection, null, null, orderBy) val cursor = context.contentResolver.query(contentUri, projection, selection, selectionArgs, orderBy)
if (cursor != null) { if (cursor != null) {
val contentUriContainsId = when (contentUri) { val contentUriContainsId = when (contentUri) {
IMAGE_CONTENT_URI, VIDEO_CONTENT_URI -> false IMAGE_CONTENT_URI, VIDEO_CONTENT_URI -> false

View file

@ -16,13 +16,15 @@ abstract class MetadataDb {
Future<void> reset(); Future<void> reset();
Future<void> removeIds(Set<int> ids, {Set<EntryDataType>? dataTypes}); Future<void> removeIds(Iterable<int> ids, {Set<EntryDataType>? dataTypes});
// entries // entries
Future<void> clearEntries(); Future<void> clearEntries();
Future<Set<AvesEntry>> loadAllEntries(); Future<Set<AvesEntry>> loadEntries({String? directory});
Future<Set<AvesEntry>> loadEntriesById(Iterable<int> ids);
Future<void> saveEntries(Iterable<AvesEntry> entries); Future<void> saveEntries(Iterable<AvesEntry> entries);
@ -30,8 +32,6 @@ abstract class MetadataDb {
Future<Set<AvesEntry>> searchEntries(String query, {int? limit}); Future<Set<AvesEntry>> searchEntries(String query, {int? limit});
Future<Set<AvesEntry>> loadEntries(List<int> ids);
// date taken // date taken
Future<void> clearDates(); Future<void> clearDates();
@ -40,19 +40,23 @@ abstract class MetadataDb {
// catalog metadata // catalog metadata
Future<void> clearMetadataEntries(); Future<void> clearCatalogMetadata();
Future<List<CatalogMetadata>> loadAllMetadataEntries(); Future<Set<CatalogMetadata>> loadCatalogMetadata();
Future<void> saveMetadata(Set<CatalogMetadata> metadataEntries); Future<Set<CatalogMetadata>> loadCatalogMetadataById(Iterable<int> ids);
Future<void> updateMetadata(int id, CatalogMetadata? metadata); Future<void> saveCatalogMetadata(Set<CatalogMetadata> metadataEntries);
Future<void> updateCatalogMetadata(int id, CatalogMetadata? metadata);
// address // address
Future<void> clearAddresses(); Future<void> clearAddresses();
Future<Set<AddressDetails>> loadAllAddresses(); Future<Set<AddressDetails>> loadAddresses();
Future<Set<AddressDetails>> loadAddressesById(Iterable<int> ids);
Future<void> saveAddresses(Set<AddressDetails> addresses); Future<void> saveAddresses(Set<AddressDetails> addresses);
@ -100,5 +104,5 @@ abstract class MetadataDb {
Future<void> addVideoPlayback(Set<VideoPlaybackRow> rows); Future<void> addVideoPlayback(Set<VideoPlaybackRow> rows);
Future<void> removeVideoPlayback(Set<int> ids); Future<void> removeVideoPlayback(Iterable<int> ids);
} }

View file

@ -119,7 +119,7 @@ class SqfliteMetadataDb implements MetadataDb {
} }
@override @override
Future<void> removeIds(Set<int> ids, {Set<EntryDataType>? dataTypes}) async { Future<void> removeIds(Iterable<int> ids, {Set<EntryDataType>? dataTypes}) async {
if (ids.isEmpty) return; if (ids.isEmpty) return;
final _dataTypes = dataTypes ?? EntryDataType.values.toSet(); final _dataTypes = dataTypes ?? EntryDataType.values.toSet();
@ -159,27 +159,19 @@ class SqfliteMetadataDb implements MetadataDb {
} }
@override @override
Future<Set<AvesEntry>> loadAllEntries() async { Future<Set<AvesEntry>> loadEntries({String? directory}) async {
final rows = await _db.query(entryTable); String? where;
List<Object?>? whereArgs;
if (directory != null) {
where = 'path LIKE ?';
whereArgs = ['$directory%'];
}
final rows = await _db.query(entryTable, where: where, whereArgs: whereArgs);
return rows.map(AvesEntry.fromMap).toSet(); return rows.map(AvesEntry.fromMap).toSet();
} }
@override @override
Future<Set<AvesEntry>> loadEntries(List<int> ids) async { Future<Set<AvesEntry>> loadEntriesById(Iterable<int> ids) => _getByIds(ids, entryTable, AvesEntry.fromMap);
if (ids.isEmpty) return {};
final entries = <AvesEntry>{};
await Future.forEach(ids, (id) async {
final rows = await _db.query(
entryTable,
where: 'id = ?',
whereArgs: [id],
);
if (rows.isNotEmpty) {
entries.add(AvesEntry.fromMap(rows.first));
}
});
return entries;
}
@override @override
Future<void> saveEntries(Iterable<AvesEntry> entries) async { Future<void> saveEntries(Iterable<AvesEntry> entries) async {
@ -236,19 +228,22 @@ class SqfliteMetadataDb implements MetadataDb {
// catalog metadata // catalog metadata
@override @override
Future<void> clearMetadataEntries() async { Future<void> clearCatalogMetadata() async {
final count = await _db.delete(metadataTable, where: '1'); final count = await _db.delete(metadataTable, where: '1');
debugPrint('$runtimeType clearMetadataEntries deleted $count rows'); debugPrint('$runtimeType clearMetadataEntries deleted $count rows');
} }
@override @override
Future<List<CatalogMetadata>> loadAllMetadataEntries() async { Future<Set<CatalogMetadata>> loadCatalogMetadata() async {
final rows = await _db.query(metadataTable); final rows = await _db.query(metadataTable);
return rows.map(CatalogMetadata.fromMap).toList(); return rows.map(CatalogMetadata.fromMap).toSet();
} }
@override @override
Future<void> saveMetadata(Set<CatalogMetadata> metadataEntries) async { Future<Set<CatalogMetadata>> loadCatalogMetadataById(Iterable<int> ids) => _getByIds(ids, metadataTable, CatalogMetadata.fromMap);
@override
Future<void> saveCatalogMetadata(Set<CatalogMetadata> metadataEntries) async {
if (metadataEntries.isEmpty) return; if (metadataEntries.isEmpty) return;
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
try { try {
@ -262,7 +257,7 @@ class SqfliteMetadataDb implements MetadataDb {
} }
@override @override
Future<void> updateMetadata(int id, CatalogMetadata? metadata) async { Future<void> updateCatalogMetadata(int id, CatalogMetadata? metadata) async {
final batch = _db.batch(); final batch = _db.batch();
batch.delete(dateTakenTable, where: 'id = ?', whereArgs: [id]); batch.delete(dateTakenTable, where: 'id = ?', whereArgs: [id]);
batch.delete(metadataTable, where: 'id = ?', whereArgs: [id]); batch.delete(metadataTable, where: 'id = ?', whereArgs: [id]);
@ -298,11 +293,14 @@ class SqfliteMetadataDb implements MetadataDb {
} }
@override @override
Future<Set<AddressDetails>> loadAllAddresses() async { Future<Set<AddressDetails>> loadAddresses() async {
final rows = await _db.query(addressTable); final rows = await _db.query(addressTable);
return rows.map(AddressDetails.fromMap).toSet(); return rows.map(AddressDetails.fromMap).toSet();
} }
@override
Future<Set<AddressDetails>> loadAddressesById(Iterable<int> ids) => _getByIds(ids, addressTable, AddressDetails.fromMap);
@override @override
Future<void> saveAddresses(Set<AddressDetails> addresses) async { Future<void> saveAddresses(Set<AddressDetails> addresses) async {
if (addresses.isEmpty) return; if (addresses.isEmpty) return;
@ -502,7 +500,7 @@ class SqfliteMetadataDb implements MetadataDb {
} }
@override @override
Future<void> removeVideoPlayback(Set<int> ids) async { Future<void> removeVideoPlayback(Iterable<int> ids) async {
if (ids.isEmpty) return; if (ids.isEmpty) return;
// using array in `whereArgs` and using it with `where filter IN ?` is a pain, so we prefer `batch` instead // using array in `whereArgs` and using it with `where filter IN ?` is a pain, so we prefer `batch` instead
@ -510,4 +508,15 @@ class SqfliteMetadataDb implements MetadataDb {
ids.forEach((id) => batch.delete(videoPlaybackTable, where: 'id = ?', whereArgs: [id])); ids.forEach((id) => batch.delete(videoPlaybackTable, where: 'id = ?', whereArgs: [id]));
await batch.commit(noResult: true); await batch.commit(noResult: true);
} }
// convenience methods
Future<Set<T>> _getByIds<T>(Iterable<int> ids, String table, T Function(Map<String, Object?> row) mapRow) async {
if (ids.isEmpty) return {};
final rows = await _db.query(
table,
where: 'id IN (${ids.join(',')})',
);
return rows.map(mapRow).toSet();
}
} }

View file

@ -643,7 +643,7 @@ class AvesEntry {
if (persist) { if (persist) {
await metadataDb.saveEntries({this}); await metadataDb.saveEntries({this});
if (catalogMetadata != null) await metadataDb.saveMetadata({catalogMetadata!}); if (catalogMetadata != null) await metadataDb.saveCatalogMetadata({catalogMetadata!});
} }
await _onVisualFieldChanged(oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); await _onVisualFieldChanged(oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped);

View file

@ -259,7 +259,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
); );
} }
// convenience // convenience methods
// This method checks whether the item already has a metadata date, // This method checks whether the item already has a metadata date,
// and adds a date (the file modified date) via Exif if possible. // and adds a date (the file modified date) via Exif if possible.

View file

@ -32,7 +32,7 @@ class CollectionLens with ChangeNotifier {
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier(); final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
int? id; int? id;
bool listenToSource; bool listenToSource, groupBursts;
List<AvesEntry>? fixedSelection; List<AvesEntry>? fixedSelection;
List<AvesEntry> _filteredSortedEntries = []; List<AvesEntry> _filteredSortedEntries = [];
@ -44,6 +44,7 @@ class CollectionLens with ChangeNotifier {
Set<CollectionFilter?>? filters, Set<CollectionFilter?>? filters,
this.id, this.id,
this.listenToSource = true, this.listenToSource = true,
this.groupBursts = true,
this.fixedSelection, this.fixedSelection,
}) : filters = (filters ?? {}).whereNotNull().toSet(), }) : filters = (filters ?? {}).whereNotNull().toSet(),
sectionFactor = settings.collectionSectionFactor, sectionFactor = settings.collectionSectionFactor,
@ -174,8 +175,6 @@ class CollectionLens with ChangeNotifier {
filterChangeNotifier.notifyListeners(); filterChangeNotifier.notifyListeners();
} }
final bool groupBursts = true;
void _applyFilters() { void _applyFilters() {
final entries = fixedSelection ?? (filters.contains(TrashFilter.instance) ? source.trashedEntries : source.visibleEntries); final entries = fixedSelection ?? (filters.contains(TrashFilter.instance) ? source.trashedEntries : source.visibleEntries);
_filteredSortedEntries = List.of(filters.isEmpty ? entries : entries.where((entry) => filters.every((filter) => filter.test(entry)))); _filteredSortedEntries = List.of(filters.isEmpty ? entries : entries.where((entry) => filters.every((filter) => filter.test(entry))));

View file

@ -25,6 +25,8 @@ import 'package:collection/collection.dart';
import 'package:event_bus/event_bus.dart'; import 'package:event_bus/event_bus.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
enum SourceInitializationState { none, directory, full }
mixin SourceBase { mixin SourceBase {
EventBus get eventBus; EventBus get eventBus;
@ -222,7 +224,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
if (persist) { if (persist) {
final id = entry.id; final id = entry.id;
await metadataDb.updateEntry(id, entry); await metadataDb.updateEntry(id, entry);
await metadataDb.updateMetadata(id, entry.catalogMetadata); await metadataDb.updateCatalogMetadata(id, entry.catalogMetadata);
await metadataDb.updateAddress(id, entry.addressDetails); await metadataDb.updateAddress(id, entry.addressDetails);
await metadataDb.updateTrash(id, entry.trashDetails); await metadataDb.updateTrash(id, entry.trashDetails);
} }
@ -315,7 +317,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
} }
}); });
await metadataDb.saveEntries(movedEntries); await metadataDb.saveEntries(movedEntries);
await metadataDb.saveMetadata(movedEntries.map((entry) => entry.catalogMetadata).whereNotNull().toSet()); await metadataDb.saveCatalogMetadata(movedEntries.map((entry) => entry.catalogMetadata).whereNotNull().toSet());
await metadataDb.saveAddresses(movedEntries.map((entry) => entry.addressDetails).whereNotNull().toSet()); await metadataDb.saveAddresses(movedEntries.map((entry) => entry.addressDetails).whereNotNull().toSet());
} else { } else {
await Future.forEach<MoveOpEvent>(movedOps, (movedOp) async { await Future.forEach<MoveOpEvent>(movedOps, (movedOp) async {
@ -349,11 +351,13 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
eventBus.fire(EntryMovedEvent(moveType, movedEntries)); eventBus.fire(EntryMovedEvent(moveType, movedEntries));
} }
bool get initialized => false; SourceInitializationState get initState => SourceInitializationState.none;
Future<void> init(); Future<void> init({
AnalysisController? analysisController,
Future<void> refresh({AnalysisController? analysisController}); String? directory,
bool loadTopEntriesFirst = false,
});
Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController}); Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController});
@ -363,7 +367,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
// update/delete in DB // update/delete in DB
final id = entry.id; final id = entry.id;
if (dataTypes.contains(EntryDataType.catalog)) { if (dataTypes.contains(EntryDataType.catalog)) {
await metadataDb.updateMetadata(id, entry.catalogMetadata); await metadataDb.updateCatalogMetadata(id, entry.catalogMetadata);
onCatalogMetadataChanged(); onCatalogMetadataChanged();
} }
if (dataTypes.contains(EntryDataType.address)) { if (dataTypes.contains(EntryDataType.address)) {

View file

@ -20,8 +20,8 @@ mixin LocationMixin on SourceBase {
List<String> sortedCountries = List.unmodifiable([]); List<String> sortedCountries = List.unmodifiable([]);
List<String> sortedPlaces = List.unmodifiable([]); List<String> sortedPlaces = List.unmodifiable([]);
Future<void> loadAddresses() async { Future<void> loadAddresses({Set<int>? ids}) async {
final saved = await metadataDb.loadAllAddresses(); final saved = await (ids != null ? metadataDb.loadAddressesById(ids) : metadataDb.loadAddresses());
final idMap = entryById; final idMap = entryById;
saved.forEach((metadata) => idMap[metadata.id]?.addressDetails = metadata); saved.forEach((metadata) => idMap[metadata.id]?.addressDetails = metadata);
onAddressMetadataChanged(); onAddressMetadataChanged();

View file

@ -4,7 +4,6 @@ import 'dart:math';
import 'package:aves/model/covers.dart'; import 'package:aves/model/covers.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/favourites.dart'; import 'package:aves/model/favourites.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/analysis_controller.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
@ -15,13 +14,31 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MediaStoreSource extends CollectionSource { class MediaStoreSource extends CollectionSource {
bool _initialized = false; SourceInitializationState _initState = SourceInitializationState.none;
@override @override
bool get initialized => _initialized; SourceInitializationState get initState => _initState;
@override @override
Future<void> init() async { Future<void> init({
AnalysisController? analysisController,
String? directory,
bool loadTopEntriesFirst = false,
}) async {
if (_initState == SourceInitializationState.none) {
await _loadEssentials();
}
if (_initState != SourceInitializationState.full) {
_initState = directory != null ? SourceInitializationState.directory : SourceInitializationState.full;
}
unawaited(_loadEntries(
analysisController: analysisController,
directory: directory,
loadTopEntriesFirst: loadTopEntriesFirst,
));
}
Future<void> _loadEssentials() async {
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
stateNotifier.value = SourceState.loading; stateNotifier.value = SourceState.loading;
await metadataDb.init(); await metadataDb.init();
@ -34,35 +51,36 @@ class MediaStoreSource extends CollectionSource {
// clear catalog metadata to get correct date/times when moving to a different time zone // clear catalog metadata to get correct date/times when moving to a different time zone
debugPrint('$runtimeType clear catalog metadata to get correct date/times'); debugPrint('$runtimeType clear catalog metadata to get correct date/times');
await metadataDb.clearDates(); await metadataDb.clearDates();
await metadataDb.clearMetadataEntries(); await metadataDb.clearCatalogMetadata();
settings.catalogTimeZone = currentTimeZone; settings.catalogTimeZone = currentTimeZone;
} }
} }
await loadDates(); await loadDates();
_initialized = true; debugPrint('$runtimeType load essentials complete in ${stopwatch.elapsed.inMilliseconds}ms');
debugPrint('$runtimeType init complete in ${stopwatch.elapsed.inMilliseconds}ms');
} }
@override Future<void> _loadEntries({
Future<void> refresh({AnalysisController? analysisController}) async { AnalysisController? analysisController,
assert(_initialized); String? directory,
required bool loadTopEntriesFirst,
}) async {
debugPrint('$runtimeType refresh start'); debugPrint('$runtimeType refresh start');
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
stateNotifier.value = SourceState.loading; stateNotifier.value = SourceState.loading;
clearEntries(); clearEntries();
final Set<AvesEntry> topEntries = {}; final Set<AvesEntry> topEntries = {};
if (settings.homePage == HomePageSetting.collection) { if (loadTopEntriesFirst) {
final topIds = settings.topEntryIds; final topIds = settings.topEntryIds;
if (topIds != null) { if (topIds != null) {
debugPrint('$runtimeType refresh ${stopwatch.elapsed} load ${topIds.length} top entries'); debugPrint('$runtimeType refresh ${stopwatch.elapsed} load ${topIds.length} top entries');
topEntries.addAll(await metadataDb.loadEntries(topIds)); topEntries.addAll(await metadataDb.loadEntriesById(topIds));
addEntries(topEntries); addEntries(topEntries);
} }
} }
debugPrint('$runtimeType refresh ${stopwatch.elapsed} fetch known entries'); debugPrint('$runtimeType refresh ${stopwatch.elapsed} fetch known entries');
final knownEntries = await metadataDb.loadAllEntries(); final knownEntries = await metadataDb.loadEntries(directory: directory);
final knownLiveEntries = knownEntries.where((entry) => !entry.trashed).toSet(); final knownLiveEntries = knownEntries.where((entry) => !entry.trashed).toSet();
debugPrint('$runtimeType refresh ${stopwatch.elapsed} check obsolete entries'); debugPrint('$runtimeType refresh ${stopwatch.elapsed} check obsolete entries');
@ -79,25 +97,33 @@ class MediaStoreSource extends CollectionSource {
addEntries(knownEntries); addEntries(knownEntries);
debugPrint('$runtimeType refresh ${stopwatch.elapsed} load metadata'); debugPrint('$runtimeType refresh ${stopwatch.elapsed} load metadata');
await loadCatalogMetadata(); if (directory != null) {
await loadAddresses(); final ids = knownLiveEntries.map((entry) => entry.id).toSet();
updateDerivedFilters(); await loadCatalogMetadata(ids: ids);
await loadAddresses(ids: ids);
} else {
await loadCatalogMetadata();
await loadAddresses();
updateDerivedFilters();
}
// clean up obsolete entries // clean up obsolete entries
debugPrint('$runtimeType refresh ${stopwatch.elapsed} remove obsolete entries'); debugPrint('$runtimeType refresh ${stopwatch.elapsed} remove obsolete entries');
await metadataDb.removeIds(obsoleteContentIds); await metadataDb.removeIds(obsoleteContentIds);
// trash if (directory != null) {
await loadTrashDetails(); // trash
unawaited(deleteExpiredTrash().then( await loadTrashDetails();
(deletedUris) { unawaited(deleteExpiredTrash().then(
if (deletedUris.isNotEmpty) { (deletedUris) {
debugPrint('evicted ${deletedUris.length} expired items from the trash'); if (deletedUris.isNotEmpty) {
removeEntries(deletedUris, includeTrash: true); debugPrint('evicted ${deletedUris.length} expired items from the trash');
} removeEntries(deletedUris, includeTrash: true);
}, }
onError: (error) => debugPrint('failed to evict expired trash error=$error'), },
)); onError: (error) => debugPrint('failed to evict expired trash error=$error'),
));
}
// verify paths because some apps move files without updating their `last modified date` // verify paths because some apps move files without updating their `last modified date`
debugPrint('$runtimeType refresh ${stopwatch.elapsed} check obsolete paths'); debugPrint('$runtimeType refresh ${stopwatch.elapsed} check obsolete paths');
@ -120,7 +146,7 @@ class MediaStoreSource extends CollectionSource {
pendingNewEntries.clear(); pendingNewEntries.clear();
} }
mediaStoreService.getEntries(knownDateByContentId).listen( mediaStoreService.getEntries(knownDateByContentId, directory: directory).listen(
(entry) { (entry) {
entry.id = metadataDb.nextId; entry.id = metadataDb.nextId;
pendingNewEntries.add(entry); pendingNewEntries.add(entry);
@ -162,7 +188,7 @@ class MediaStoreSource extends CollectionSource {
// sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg` // sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg`
@override @override
Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController}) async { Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController}) async {
if (!_initialized || !isMonitoring) return changedUris; if (_initState == SourceInitializationState.none || !isMonitoring) return changedUris;
debugPrint('$runtimeType refreshUris ${changedUris.length} uris'); debugPrint('$runtimeType refreshUris ${changedUris.length} uris');
final uriByContentId = Map.fromEntries(changedUris.map((uri) { final uriByContentId = Map.fromEntries(changedUris.map((uri) {

View file

@ -14,8 +14,8 @@ mixin TagMixin on SourceBase {
List<String> sortedTags = List.unmodifiable([]); List<String> sortedTags = List.unmodifiable([]);
Future<void> loadCatalogMetadata() async { Future<void> loadCatalogMetadata({Set<int>? ids}) async {
final saved = await metadataDb.loadAllMetadataEntries(); final saved = await (ids != null ? metadataDb.loadCatalogMetadataById(ids) : metadataDb.loadCatalogMetadata());
final idMap = entryById; final idMap = entryById;
saved.forEach((metadata) => idMap[metadata.id]?.catalogMetadata = metadata); saved.forEach((metadata) => idMap[metadata.id]?.catalogMetadata = metadata);
onCatalogMetadataChanged(); onCatalogMetadataChanged();
@ -42,7 +42,7 @@ mixin TagMixin on SourceBase {
if (entry.isCatalogued) { if (entry.isCatalogued) {
newMetadata.add(entry.catalogMetadata!); newMetadata.add(entry.catalogMetadata!);
if (newMetadata.length >= commitCountThreshold) { if (newMetadata.length >= commitCountThreshold) {
await metadataDb.saveMetadata(Set.unmodifiable(newMetadata)); await metadataDb.saveCatalogMetadata(Set.unmodifiable(newMetadata));
onCatalogMetadataChanged(); onCatalogMetadataChanged();
newMetadata.clear(); newMetadata.clear();
} }
@ -53,7 +53,7 @@ mixin TagMixin on SourceBase {
} }
setProgress(done: ++progressDone, total: progressTotal); setProgress(done: ++progressDone, total: progressTotal);
} }
await metadataDb.saveMetadata(Set.unmodifiable(newMetadata)); await metadataDb.saveCatalogMetadata(Set.unmodifiable(newMetadata));
onCatalogMetadataChanged(); onCatalogMetadataChanged();
} }

View file

@ -115,8 +115,7 @@ class Analyzer {
settings.systemLocalesFallback = await deviceService.getLocales(); settings.systemLocalesFallback = await deviceService.getLocales();
_l10n = await AppLocalizations.delegate.load(settings.appliedLocale); _l10n = await AppLocalizations.delegate.load(settings.appliedLocale);
_serviceStateNotifier.value = AnalyzerState.running; _serviceStateNotifier.value = AnalyzerState.running;
await _source.init(); await _source.init(analysisController: _controller);
unawaited(_source.refresh(analysisController: _controller));
_notificationUpdateTimer = Timer.periodic(notificationUpdateInterval, (_) async { _notificationUpdateTimer = Timer.periodic(notificationUpdateInterval, (_) async {
if (!isRunning) return; if (!isRunning) return;

View file

@ -11,7 +11,7 @@ abstract class MediaStoreService {
Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById); Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById);
// knownEntries: map of contentId -> dateModifiedSecs // knownEntries: map of contentId -> dateModifiedSecs
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries); Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory});
// returns media URI // returns media URI
Future<Uri?> scanFile(String path, String mimeType); Future<Uri?> scanFile(String path, String mimeType);
@ -48,11 +48,12 @@ class PlatformMediaStoreService implements MediaStoreService {
} }
@override @override
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries) { Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
try { try {
return _streamChannel return _streamChannel
.receiveBroadcastStream(<String, dynamic>{ .receiveBroadcastStream(<String, dynamic>{
'knownEntries': knownEntries, 'knownEntries': knownEntries,
'directory': directory,
}) })
.where((event) => event is Map) .where((event) => event is Map)
.map((event) => AvesEntry.fromMap(event as Map)); .map((event) => AvesEntry.fromMap(event as Map));

View file

@ -168,7 +168,16 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
debugPrint('$runtimeType lifecycle ${state.name}'); debugPrint('$runtimeType lifecycle ${state.name}');
switch (state) { switch (state) {
case AppLifecycleState.inactive: case AppLifecycleState.inactive:
_saveTopEntries(); switch (appModeNotifier.value) {
case AppMode.main:
case AppMode.pickMediaExternal:
_saveTopEntries();
break;
case AppMode.pickMediaInternal:
case AppMode.pickFilterInternal:
case AppMode.view:
break;
}
break; break;
case AppLifecycleState.paused: case AppLifecycleState.paused:
case AppLifecycleState.detached: case AppLifecycleState.detached:

View file

@ -96,35 +96,46 @@ class _CollectionGridContent extends StatelessWidget {
final scrollableWidth = c.item1; final scrollableWidth = c.item1;
final columnCount = c.item2; final columnCount = c.item2;
final tileSpacing = c.item3; final tileSpacing = c.item3;
// do not listen for animation delay change
final target = context.read<DurationsData>().staggeredAnimationPageTarget;
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(target);
return GridTheme( return GridTheme(
extent: thumbnailExtent, extent: thumbnailExtent,
child: EntryListDetailsTheme( child: EntryListDetailsTheme(
extent: thumbnailExtent, extent: thumbnailExtent,
child: SectionedEntryListLayoutProvider( child: ValueListenableBuilder<SourceState>(
collection: collection, valueListenable: collection.source.stateNotifier,
scrollableWidth: scrollableWidth, builder: (context, sourceState, child) {
tileLayout: tileLayout, late final Duration tileAnimationDelay;
columnCount: columnCount, if (sourceState == SourceState.ready) {
spacing: tileSpacing, // do not listen for animation delay change
tileExtent: thumbnailExtent, final target = context.read<DurationsData>().staggeredAnimationPageTarget;
tileBuilder: (entry) => AnimatedBuilder( tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(target);
animation: favourites, } else {
builder: (context, child) { tileAnimationDelay = Duration.zero;
return InteractiveTile( }
key: ValueKey(entry.id), return SectionedEntryListLayoutProvider(
collection: collection, collection: collection,
entry: entry, scrollableWidth: scrollableWidth,
thumbnailExtent: thumbnailExtent, tileLayout: tileLayout,
tileLayout: tileLayout, columnCount: columnCount,
isScrollingNotifier: _isScrollingNotifier, spacing: tileSpacing,
); tileExtent: thumbnailExtent,
}, tileBuilder: (entry) => AnimatedBuilder(
), animation: favourites,
tileAnimationDelay: tileAnimationDelay, builder: (context, child) {
child: child!, return InteractiveTile(
key: ValueKey(entry.id),
collection: collection,
entry: entry,
thumbnailExtent: thumbnailExtent,
tileLayout: tileLayout,
isScrollingNotifier: _isScrollingNotifier,
);
},
),
tileAnimationDelay: tileAnimationDelay,
child: child!,
);
},
child: child,
), ),
), ),
); );

View file

@ -51,10 +51,9 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
} }
final source = context.read<CollectionSource>(); final source = context.read<CollectionSource>();
if (!source.initialized) { if (source.initState != SourceInitializationState.full) {
// source may be uninitialized in viewer mode // source may be uninitialized in viewer mode
await source.init(); await source.init();
unawaited(source.refresh());
} }
final entriesByDestination = <String, Set<AvesEntry>>{}; final entriesByDestination = <String, Set<AvesEntry>>{};

View file

@ -8,6 +8,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/services/analysis_service.dart'; import 'package:aves/services/analysis_service.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
@ -131,11 +132,16 @@ class _AppDebugPageState extends State<AppDebugPage> {
title: const Text('Show tasks overlay'), title: const Text('Show tasks overlay'),
), ),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () => source.init(loadTopEntriesFirst: false),
await source.init(); child: const Text('Source refresh (top off)'),
await source.refresh(); ),
}, ElevatedButton(
child: const Text('Source full refresh'), onPressed: () => source.init(loadTopEntriesFirst: true),
child: const Text('Source refresh (top on)'),
),
ElevatedButton(
onPressed: () => source.init(directory: '${androidFileUtils.dcimPath}/Camera'),
child: const Text('Source refresh (camera)'),
), ),
ElevatedButton( ElevatedButton(
onPressed: () => AnalysisService.startService(force: false), onPressed: () => AnalysisService.startService(force: false),

View file

@ -21,7 +21,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
late Future<int> _dbFileSizeLoader; late Future<int> _dbFileSizeLoader;
late Future<Set<AvesEntry>> _dbEntryLoader; late Future<Set<AvesEntry>> _dbEntryLoader;
late Future<Map<int?, int?>> _dbDateLoader; late Future<Map<int?, int?>> _dbDateLoader;
late Future<List<CatalogMetadata>> _dbMetadataLoader; late Future<Set<CatalogMetadata>> _dbMetadataLoader;
late Future<Set<AddressDetails>> _dbAddressLoader; late Future<Set<AddressDetails>> _dbAddressLoader;
late Future<Set<TrashDetails>> _dbTrashLoader; late Future<Set<TrashDetails>> _dbTrashLoader;
late Future<Set<FavouriteRow>> _dbFavouritesLoader; late Future<Set<FavouriteRow>> _dbFavouritesLoader;
@ -108,7 +108,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
); );
}, },
), ),
FutureBuilder<List>( FutureBuilder<Set>(
future: _dbMetadataLoader, future: _dbMetadataLoader,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) return Text(snapshot.error.toString()); if (snapshot.hasError) return Text(snapshot.error.toString());
@ -122,7 +122,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
onPressed: () => metadataDb.clearMetadataEntries().then((_) => _startDbReport()), onPressed: () => metadataDb.clearCatalogMetadata().then((_) => _startDbReport()),
child: const Text('Clear'), child: const Text('Clear'),
), ),
], ],
@ -243,10 +243,10 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
void _startDbReport() { void _startDbReport() {
_dbFileSizeLoader = metadataDb.dbFileSize(); _dbFileSizeLoader = metadataDb.dbFileSize();
_dbEntryLoader = metadataDb.loadAllEntries(); _dbEntryLoader = metadataDb.loadEntries();
_dbDateLoader = metadataDb.loadDates(); _dbDateLoader = metadataDb.loadDates();
_dbMetadataLoader = metadataDb.loadAllMetadataEntries(); _dbMetadataLoader = metadataDb.loadCatalogMetadata();
_dbAddressLoader = metadataDb.loadAllAddresses(); _dbAddressLoader = metadataDb.loadAddresses();
_dbTrashLoader = metadataDb.loadAllTrashDetails(); _dbTrashLoader = metadataDb.loadAllTrashDetails();
_dbFavouritesLoader = metadataDb.loadAllFavourites(); _dbFavouritesLoader = metadataDb.loadAllFavourites();
_dbCoversLoader = metadataDb.loadAllCovers(); _dbCoversLoader = metadataDb.loadAllCovers();

View file

@ -4,6 +4,7 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart'; import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -116,14 +117,33 @@ class _HomePageState extends State<HomePage> {
} }
context.read<ValueNotifier<AppMode>>().value = appMode; context.read<ValueNotifier<AppMode>>().value = appMode;
unawaited(reportService.setCustomKey('app_mode', appMode.toString())); unawaited(reportService.setCustomKey('app_mode', appMode.toString()));
debugPrint('Storage check complete in ${stopwatch.elapsed.inMilliseconds}ms');
if (appMode != AppMode.view || _isViewerSourceable(_viewerEntry!)) { switch (appMode) {
debugPrint('Storage check complete in ${stopwatch.elapsed.inMilliseconds}ms'); case AppMode.main:
unawaited(GlobalSearch.registerCallback()); case AppMode.pickMediaExternal:
unawaited(AnalysisService.registerCallback()); unawaited(GlobalSearch.registerCallback());
final source = context.read<CollectionSource>(); unawaited(AnalysisService.registerCallback());
await source.init(); final source = context.read<CollectionSource>();
unawaited(source.refresh()); await source.init(
loadTopEntriesFirst: settings.homePage == HomePageSetting.collection,
);
break;
case AppMode.view:
if (_isViewerSourceable(_viewerEntry)) {
final directory = _viewerEntry?.directory;
if (directory != null) {
unawaited(AnalysisService.registerCallback());
final source = context.read<CollectionSource>();
await source.init(
directory: directory,
);
}
}
break;
case AppMode.pickMediaInternal:
case AppMode.pickFilterInternal:
break;
} }
// `pushReplacement` is not enough in some edge cases // `pushReplacement` is not enough in some edge cases
@ -135,7 +155,9 @@ class _HomePageState extends State<HomePage> {
)); ));
} }
bool _isViewerSourceable(AvesEntry viewerEntry) => viewerEntry.directory != null && !settings.hiddenFilters.any((filter) => filter.test(viewerEntry)); bool _isViewerSourceable(AvesEntry? viewerEntry) {
return viewerEntry != null && viewerEntry.directory != null && !settings.hiddenFilters.any((filter) => filter.test(viewerEntry));
}
Future<AvesEntry?> _initViewerEntry({required String uri, required String? mimeType}) async { Future<AvesEntry?> _initViewerEntry({required String uri, required String? mimeType}) async {
if (uri.startsWith('/')) { if (uri.startsWith('/')) {
@ -156,7 +178,7 @@ class _HomePageState extends State<HomePage> {
CollectionLens? collection; CollectionLens? collection;
final source = context.read<CollectionSource>(); final source = context.read<CollectionSource>();
if (source.initialized) { if (source.initState != SourceInitializationState.none) {
final album = viewerEntry.directory; final album = viewerEntry.directory;
if (album != null) { if (album != null) {
// wait for collection to pass the `loading` state // wait for collection to pass the `loading` state
@ -174,6 +196,11 @@ class _HomePageState extends State<HomePage> {
collection = CollectionLens( collection = CollectionLens(
source: source, source: source,
filters: {AlbumFilter(album, source.getAlbumDisplayName(context, album))}, filters: {AlbumFilter(album, source.getAlbumDisplayName(context, album))},
listenToSource: false,
// if we group bursts, opening a burst sub-entry should:
// - identify and select the containing main entry,
// - select the sub-entry in the Viewer page.
groupBursts: false,
); );
final viewerEntryPath = viewerEntry.path; final viewerEntryPath = viewerEntry.path;
final collectionEntry = collection.sortedEntries.firstWhereOrNull((entry) => entry.path == viewerEntryPath); final collectionEntry = collection.sortedEntries.firstWhereOrNull((entry) => entry.path == viewerEntryPath);

View file

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/app_mode.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/coordinate.dart'; import 'package:aves/model/filters/coordinate.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
@ -371,6 +372,9 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
} }
void _goToCollection(CollectionFilter filter) { void _goToCollection(CollectionFilter filter) {
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
if (!isMainMode) return;
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
context, context,
MaterialPageRoute( MaterialPageRoute(

View file

@ -188,7 +188,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
showFeedback(context, l10n.genericFailureFeedback); showFeedback(context, l10n.genericFailureFeedback);
} else { } else {
final source = context.read<CollectionSource>(); final source = context.read<CollectionSource>();
if (source.initialized) { if (source.initState != SourceInitializationState.none) {
await source.removeEntries({entry.uri}, includeTrash: true); await source.removeEntries({entry.uri}, includeTrash: true);
} }
EntryRemovedNotification(entry).dispatch(context); EntryRemovedNotification(entry).dispatch(context);
@ -203,9 +203,8 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
if (options == null) return; if (options == null) return;
final source = context.read<CollectionSource>(); final source = context.read<CollectionSource>();
if (!source.initialized) { if (source.initState != SourceInitializationState.full) {
await source.init(); await source.init();
unawaited(source.refresh());
} }
final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export); final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export);
if (destinationAlbum == null) return; if (destinationAlbum == null) return;

View file

@ -39,9 +39,9 @@ class _DbTabState extends State<DbTab> {
void _loadDatabase() { void _loadDatabase() {
final id = entry.id; final id = entry.id;
_dbDateLoader = metadataDb.loadDates().then((values) => values[id]); _dbDateLoader = metadataDb.loadDates().then((values) => values[id]);
_dbEntryLoader = metadataDb.loadAllEntries().then((values) => values.firstWhereOrNull((row) => row.id == id)); _dbEntryLoader = metadataDb.loadEntries().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbMetadataLoader = metadataDb.loadAllMetadataEntries().then((values) => values.firstWhereOrNull((row) => row.id == id)); _dbMetadataLoader = metadataDb.loadCatalogMetadata().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbAddressLoader = metadataDb.loadAllAddresses().then((values) => values.firstWhereOrNull((row) => row.id == id)); _dbAddressLoader = metadataDb.loadAddresses().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbTrashDetailsLoader = metadataDb.loadAllTrashDetails().then((values) => values.firstWhereOrNull((row) => row.id == id)); _dbTrashDetailsLoader = metadataDb.loadAllTrashDetails().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbVideoPlaybackLoader = metadataDb.loadVideoPlayback(id); _dbVideoPlaybackLoader = metadataDb.loadVideoPlayback(id);
setState(() {}); setState(() {});

View file

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart'; import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
@ -399,8 +400,12 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
} }
void _goToCollection(CollectionFilter filter) { void _goToCollection(CollectionFilter filter) {
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
if (!isMainMode) return;
final baseCollection = collection; final baseCollection = collection;
if (baseCollection == null) return; if (baseCollection == null) return;
_onLeave(); _onLeave();
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
context, context,

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/entry_info_actions.dart'; import 'package:aves/model/actions/entry_info_actions.dart';
import 'package:aves/model/actions/events.dart'; import 'package:aves/model/actions/events.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
@ -199,7 +198,7 @@ class _InfoPageContentState extends State<_InfoPageContent> {
collection: collection, collection: collection,
actionDelegate: _actionDelegate, actionDelegate: _actionDelegate,
isEditingMetadataNotifier: _isEditingMetadataNotifier, isEditingMetadataNotifier: _isEditingMetadataNotifier,
onFilter: _goToCollection, onFilter: _onFilter,
); );
final locationAtTop = widget.split && entry.hasGps; final locationAtTop = widget.split && entry.hasGps;
final locationSection = LocationSection( final locationSection = LocationSection(
@ -207,7 +206,7 @@ class _InfoPageContentState extends State<_InfoPageContent> {
entry: entry, entry: entry,
showTitle: !locationAtTop, showTitle: !locationAtTop,
isScrollingNotifier: widget.isScrollingNotifier, isScrollingNotifier: widget.isScrollingNotifier,
onFilter: _goToCollection, onFilter: _onFilter,
); );
final basicAndLocationSliver = locationAtTop final basicAndLocationSliver = locationAtTop
? SliverToBoxAdapter( ? SliverToBoxAdapter(
@ -265,9 +264,5 @@ class _InfoPageContentState extends State<_InfoPageContent> {
}); });
} }
void _goToCollection(CollectionFilter filter) { void _onFilter(CollectionFilter filter) => FilterSelectedNotification(filter).dispatch(context);
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
if (!isMainMode || collection == null) return;
FilterSelectedNotification(filter).dispatch(context);
}
} }

View file

@ -15,7 +15,7 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById) => SynchronousFuture([]); Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById) => SynchronousFuture([]);
@override @override
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries) => Stream.fromIterable(entries); Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) => Stream.fromIterable(entries);
static var _lastId = 1; static var _lastId = 1;

View file

@ -19,12 +19,12 @@ class FakeMetadataDb extends Fake implements MetadataDb {
Future<void> init() => SynchronousFuture(null); Future<void> init() => SynchronousFuture(null);
@override @override
Future<void> removeIds(Set<int> ids, {Set<EntryDataType>? dataTypes}) => SynchronousFuture(null); Future<void> removeIds(Iterable<int> ids, {Set<EntryDataType>? dataTypes}) => SynchronousFuture(null);
// entries // entries
@override @override
Future<Set<AvesEntry>> loadAllEntries() => SynchronousFuture({}); Future<Set<AvesEntry>> loadEntries({String? directory}) => SynchronousFuture({});
@override @override
Future<void> saveEntries(Iterable<AvesEntry> entries) => SynchronousFuture(null); Future<void> saveEntries(Iterable<AvesEntry> entries) => SynchronousFuture(null);
@ -40,18 +40,18 @@ class FakeMetadataDb extends Fake implements MetadataDb {
// catalog metadata // catalog metadata
@override @override
Future<List<CatalogMetadata>> loadAllMetadataEntries() => SynchronousFuture([]); Future<Set<CatalogMetadata>> loadCatalogMetadata() => SynchronousFuture({});
@override @override
Future<void> saveMetadata(Set<CatalogMetadata> metadataEntries) => SynchronousFuture(null); Future<void> saveCatalogMetadata(Set<CatalogMetadata> metadataEntries) => SynchronousFuture(null);
@override @override
Future<void> updateMetadata(int id, CatalogMetadata? metadata) => SynchronousFuture(null); Future<void> updateCatalogMetadata(int id, CatalogMetadata? metadata) => SynchronousFuture(null);
// address // address
@override @override
Future<Set<AddressDetails>> loadAllAddresses() => SynchronousFuture({}); Future<Set<AddressDetails>> loadAddresses() => SynchronousFuture({});
@override @override
Future<void> saveAddresses(Set<AddressDetails> addresses) => SynchronousFuture(null); Future<void> saveAddresses(Set<AddressDetails> addresses) => SynchronousFuture(null);
@ -101,5 +101,5 @@ class FakeMetadataDb extends Fake implements MetadataDb {
// video playback // video playback
@override @override
Future<void> removeVideoPlayback(Set<int> ids) => SynchronousFuture(null); Future<void> removeVideoPlayback(Iterable<int> ids) => SynchronousFuture(null);
} }

View file

@ -83,7 +83,6 @@ void main() {
} }
}); });
await source.init(); await source.init();
await source.refresh();
await readyCompleter.future; await readyCompleter.future;
return source; return source;
} }