#170 view: load dir entries only, prevent navigation from filters, do not group bursts, do not listen to source
This commit is contained in:
parent
6b4d9c0bc3
commit
b1bf026ffd
27 changed files with 273 additions and 162 deletions
|
@ -20,11 +20,13 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
|
|||
private lateinit var handler: Handler
|
||||
|
||||
private var knownEntries: Map<Int?, Int?>? = null
|
||||
private var directory: String? = null
|
||||
|
||||
init {
|
||||
if (arguments is Map<*, *>) {
|
||||
@Suppress("unchecked_cast")
|
||||
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() {
|
||||
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap()) { success(it) }
|
||||
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap(), directory) { success(it) }
|
||||
endOfStream()
|
||||
}
|
||||
|
||||
|
|
|
@ -37,13 +37,24 @@ import kotlin.coroutines.resumeWithException
|
|||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
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 knownDate = knownEntries[contentId]
|
||||
return knownDate == null || knownDate < dateModifiedSecs
|
||||
}
|
||||
fetchFrom(context, isModified, handleNewEntry, IMAGE_CONTENT_URI, IMAGE_PROJECTION)
|
||||
fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION)
|
||||
var selection: String? = null
|
||||
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,
|
||||
|
@ -138,12 +149,14 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
handleNewEntry: NewEntryHandler,
|
||||
contentUri: Uri,
|
||||
projection: Array<String>,
|
||||
selection: String? = null,
|
||||
selectionArgs: Array<String>? = null,
|
||||
fileMimeType: String? = null,
|
||||
): Boolean {
|
||||
var found = false
|
||||
val orderBy = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
|
||||
try {
|
||||
val cursor = context.contentResolver.query(contentUri, projection, null, null, orderBy)
|
||||
val cursor = context.contentResolver.query(contentUri, projection, selection, selectionArgs, orderBy)
|
||||
if (cursor != null) {
|
||||
val contentUriContainsId = when (contentUri) {
|
||||
IMAGE_CONTENT_URI, VIDEO_CONTENT_URI -> false
|
||||
|
|
|
@ -16,13 +16,15 @@ abstract class MetadataDb {
|
|||
|
||||
Future<void> reset();
|
||||
|
||||
Future<void> removeIds(Set<int> ids, {Set<EntryDataType>? dataTypes});
|
||||
Future<void> removeIds(Iterable<int> ids, {Set<EntryDataType>? dataTypes});
|
||||
|
||||
// entries
|
||||
|
||||
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);
|
||||
|
||||
|
@ -30,8 +32,6 @@ abstract class MetadataDb {
|
|||
|
||||
Future<Set<AvesEntry>> searchEntries(String query, {int? limit});
|
||||
|
||||
Future<Set<AvesEntry>> loadEntries(List<int> ids);
|
||||
|
||||
// date taken
|
||||
|
||||
Future<void> clearDates();
|
||||
|
@ -40,19 +40,23 @@ abstract class MetadataDb {
|
|||
|
||||
// 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
|
||||
|
||||
Future<void> clearAddresses();
|
||||
|
||||
Future<Set<AddressDetails>> loadAllAddresses();
|
||||
Future<Set<AddressDetails>> loadAddresses();
|
||||
|
||||
Future<Set<AddressDetails>> loadAddressesById(Iterable<int> ids);
|
||||
|
||||
Future<void> saveAddresses(Set<AddressDetails> addresses);
|
||||
|
||||
|
@ -100,5 +104,5 @@ abstract class MetadataDb {
|
|||
|
||||
Future<void> addVideoPlayback(Set<VideoPlaybackRow> rows);
|
||||
|
||||
Future<void> removeVideoPlayback(Set<int> ids);
|
||||
Future<void> removeVideoPlayback(Iterable<int> ids);
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
}
|
||||
|
||||
@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;
|
||||
|
||||
final _dataTypes = dataTypes ?? EntryDataType.values.toSet();
|
||||
|
@ -159,27 +159,19 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Set<AvesEntry>> loadAllEntries() async {
|
||||
final rows = await _db.query(entryTable);
|
||||
Future<Set<AvesEntry>> loadEntries({String? directory}) async {
|
||||
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();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Set<AvesEntry>> loadEntries(List<int> ids) async {
|
||||
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;
|
||||
}
|
||||
Future<Set<AvesEntry>> loadEntriesById(Iterable<int> ids) => _getByIds(ids, entryTable, AvesEntry.fromMap);
|
||||
|
||||
@override
|
||||
Future<void> saveEntries(Iterable<AvesEntry> entries) async {
|
||||
|
@ -236,19 +228,22 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
// catalog metadata
|
||||
|
||||
@override
|
||||
Future<void> clearMetadataEntries() async {
|
||||
Future<void> clearCatalogMetadata() async {
|
||||
final count = await _db.delete(metadataTable, where: '1');
|
||||
debugPrint('$runtimeType clearMetadataEntries deleted $count rows');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<CatalogMetadata>> loadAllMetadataEntries() async {
|
||||
Future<Set<CatalogMetadata>> loadCatalogMetadata() async {
|
||||
final rows = await _db.query(metadataTable);
|
||||
return rows.map(CatalogMetadata.fromMap).toList();
|
||||
return rows.map(CatalogMetadata.fromMap).toSet();
|
||||
}
|
||||
|
||||
@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;
|
||||
final stopwatch = Stopwatch()..start();
|
||||
try {
|
||||
|
@ -262,7 +257,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> updateMetadata(int id, CatalogMetadata? metadata) async {
|
||||
Future<void> updateCatalogMetadata(int id, CatalogMetadata? metadata) async {
|
||||
final batch = _db.batch();
|
||||
batch.delete(dateTakenTable, where: 'id = ?', whereArgs: [id]);
|
||||
batch.delete(metadataTable, where: 'id = ?', whereArgs: [id]);
|
||||
|
@ -298,11 +293,14 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Set<AddressDetails>> loadAllAddresses() async {
|
||||
Future<Set<AddressDetails>> loadAddresses() async {
|
||||
final rows = await _db.query(addressTable);
|
||||
return rows.map(AddressDetails.fromMap).toSet();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Set<AddressDetails>> loadAddressesById(Iterable<int> ids) => _getByIds(ids, addressTable, AddressDetails.fromMap);
|
||||
|
||||
@override
|
||||
Future<void> saveAddresses(Set<AddressDetails> addresses) async {
|
||||
if (addresses.isEmpty) return;
|
||||
|
@ -502,7 +500,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> removeVideoPlayback(Set<int> ids) async {
|
||||
Future<void> removeVideoPlayback(Iterable<int> ids) async {
|
||||
if (ids.isEmpty) return;
|
||||
|
||||
// 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]));
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -643,7 +643,7 @@ class AvesEntry {
|
|||
|
||||
if (persist) {
|
||||
await metadataDb.saveEntries({this});
|
||||
if (catalogMetadata != null) await metadataDb.saveMetadata({catalogMetadata!});
|
||||
if (catalogMetadata != null) await metadataDb.saveCatalogMetadata({catalogMetadata!});
|
||||
}
|
||||
|
||||
await _onVisualFieldChanged(oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped);
|
||||
|
|
|
@ -259,7 +259,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
|||
);
|
||||
}
|
||||
|
||||
// convenience
|
||||
// convenience methods
|
||||
|
||||
// This method checks whether the item already has a metadata date,
|
||||
// and adds a date (the file modified date) via Exif if possible.
|
||||
|
|
|
@ -32,7 +32,7 @@ class CollectionLens with ChangeNotifier {
|
|||
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
int? id;
|
||||
bool listenToSource;
|
||||
bool listenToSource, groupBursts;
|
||||
List<AvesEntry>? fixedSelection;
|
||||
|
||||
List<AvesEntry> _filteredSortedEntries = [];
|
||||
|
@ -44,6 +44,7 @@ class CollectionLens with ChangeNotifier {
|
|||
Set<CollectionFilter?>? filters,
|
||||
this.id,
|
||||
this.listenToSource = true,
|
||||
this.groupBursts = true,
|
||||
this.fixedSelection,
|
||||
}) : filters = (filters ?? {}).whereNotNull().toSet(),
|
||||
sectionFactor = settings.collectionSectionFactor,
|
||||
|
@ -174,8 +175,6 @@ class CollectionLens with ChangeNotifier {
|
|||
filterChangeNotifier.notifyListeners();
|
||||
}
|
||||
|
||||
final bool groupBursts = true;
|
||||
|
||||
void _applyFilters() {
|
||||
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))));
|
||||
|
|
|
@ -25,6 +25,8 @@ import 'package:collection/collection.dart';
|
|||
import 'package:event_bus/event_bus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
enum SourceInitializationState { none, directory, full }
|
||||
|
||||
mixin SourceBase {
|
||||
EventBus get eventBus;
|
||||
|
||||
|
@ -222,7 +224,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
|||
if (persist) {
|
||||
final id = entry.id;
|
||||
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.updateTrash(id, entry.trashDetails);
|
||||
}
|
||||
|
@ -315,7 +317,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
|||
}
|
||||
});
|
||||
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());
|
||||
} else {
|
||||
await Future.forEach<MoveOpEvent>(movedOps, (movedOp) async {
|
||||
|
@ -349,11 +351,13 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
|||
eventBus.fire(EntryMovedEvent(moveType, movedEntries));
|
||||
}
|
||||
|
||||
bool get initialized => false;
|
||||
SourceInitializationState get initState => SourceInitializationState.none;
|
||||
|
||||
Future<void> init();
|
||||
|
||||
Future<void> refresh({AnalysisController? analysisController});
|
||||
Future<void> init({
|
||||
AnalysisController? analysisController,
|
||||
String? directory,
|
||||
bool loadTopEntriesFirst = false,
|
||||
});
|
||||
|
||||
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
|
||||
final id = entry.id;
|
||||
if (dataTypes.contains(EntryDataType.catalog)) {
|
||||
await metadataDb.updateMetadata(id, entry.catalogMetadata);
|
||||
await metadataDb.updateCatalogMetadata(id, entry.catalogMetadata);
|
||||
onCatalogMetadataChanged();
|
||||
}
|
||||
if (dataTypes.contains(EntryDataType.address)) {
|
||||
|
|
|
@ -20,8 +20,8 @@ mixin LocationMixin on SourceBase {
|
|||
List<String> sortedCountries = List.unmodifiable([]);
|
||||
List<String> sortedPlaces = List.unmodifiable([]);
|
||||
|
||||
Future<void> loadAddresses() async {
|
||||
final saved = await metadataDb.loadAllAddresses();
|
||||
Future<void> loadAddresses({Set<int>? ids}) async {
|
||||
final saved = await (ids != null ? metadataDb.loadAddressesById(ids) : metadataDb.loadAddresses());
|
||||
final idMap = entryById;
|
||||
saved.forEach((metadata) => idMap[metadata.id]?.addressDetails = metadata);
|
||||
onAddressMetadataChanged();
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'dart:math';
|
|||
import 'package:aves/model/covers.dart';
|
||||
import 'package:aves/model/entry.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/source/analysis_controller.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
|
@ -15,13 +14,31 @@ import 'package:collection/collection.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class MediaStoreSource extends CollectionSource {
|
||||
bool _initialized = false;
|
||||
SourceInitializationState _initState = SourceInitializationState.none;
|
||||
|
||||
@override
|
||||
bool get initialized => _initialized;
|
||||
SourceInitializationState get initState => _initState;
|
||||
|
||||
@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();
|
||||
stateNotifier.value = SourceState.loading;
|
||||
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
|
||||
debugPrint('$runtimeType clear catalog metadata to get correct date/times');
|
||||
await metadataDb.clearDates();
|
||||
await metadataDb.clearMetadataEntries();
|
||||
await metadataDb.clearCatalogMetadata();
|
||||
settings.catalogTimeZone = currentTimeZone;
|
||||
}
|
||||
}
|
||||
await loadDates();
|
||||
_initialized = true;
|
||||
debugPrint('$runtimeType init complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
||||
debugPrint('$runtimeType load essentials complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> refresh({AnalysisController? analysisController}) async {
|
||||
assert(_initialized);
|
||||
Future<void> _loadEntries({
|
||||
AnalysisController? analysisController,
|
||||
String? directory,
|
||||
required bool loadTopEntriesFirst,
|
||||
}) async {
|
||||
debugPrint('$runtimeType refresh start');
|
||||
final stopwatch = Stopwatch()..start();
|
||||
stateNotifier.value = SourceState.loading;
|
||||
clearEntries();
|
||||
|
||||
final Set<AvesEntry> topEntries = {};
|
||||
if (settings.homePage == HomePageSetting.collection) {
|
||||
if (loadTopEntriesFirst) {
|
||||
final topIds = settings.topEntryIds;
|
||||
if (topIds != null) {
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} load ${topIds.length} top entries');
|
||||
topEntries.addAll(await metadataDb.loadEntries(topIds));
|
||||
topEntries.addAll(await metadataDb.loadEntriesById(topIds));
|
||||
addEntries(topEntries);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} check obsolete entries');
|
||||
|
@ -79,25 +97,33 @@ class MediaStoreSource extends CollectionSource {
|
|||
addEntries(knownEntries);
|
||||
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} load metadata');
|
||||
await loadCatalogMetadata();
|
||||
await loadAddresses();
|
||||
updateDerivedFilters();
|
||||
if (directory != null) {
|
||||
final ids = knownLiveEntries.map((entry) => entry.id).toSet();
|
||||
await loadCatalogMetadata(ids: ids);
|
||||
await loadAddresses(ids: ids);
|
||||
} else {
|
||||
await loadCatalogMetadata();
|
||||
await loadAddresses();
|
||||
updateDerivedFilters();
|
||||
}
|
||||
|
||||
// clean up obsolete entries
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} remove obsolete entries');
|
||||
await metadataDb.removeIds(obsoleteContentIds);
|
||||
|
||||
// trash
|
||||
await loadTrashDetails();
|
||||
unawaited(deleteExpiredTrash().then(
|
||||
(deletedUris) {
|
||||
if (deletedUris.isNotEmpty) {
|
||||
debugPrint('evicted ${deletedUris.length} expired items from the trash');
|
||||
removeEntries(deletedUris, includeTrash: true);
|
||||
}
|
||||
},
|
||||
onError: (error) => debugPrint('failed to evict expired trash error=$error'),
|
||||
));
|
||||
if (directory != null) {
|
||||
// trash
|
||||
await loadTrashDetails();
|
||||
unawaited(deleteExpiredTrash().then(
|
||||
(deletedUris) {
|
||||
if (deletedUris.isNotEmpty) {
|
||||
debugPrint('evicted ${deletedUris.length} expired items from the trash');
|
||||
removeEntries(deletedUris, includeTrash: true);
|
||||
}
|
||||
},
|
||||
onError: (error) => debugPrint('failed to evict expired trash error=$error'),
|
||||
));
|
||||
}
|
||||
|
||||
// verify paths because some apps move files without updating their `last modified date`
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} check obsolete paths');
|
||||
|
@ -120,7 +146,7 @@ class MediaStoreSource extends CollectionSource {
|
|||
pendingNewEntries.clear();
|
||||
}
|
||||
|
||||
mediaStoreService.getEntries(knownDateByContentId).listen(
|
||||
mediaStoreService.getEntries(knownDateByContentId, directory: directory).listen(
|
||||
(entry) {
|
||||
entry.id = metadataDb.nextId;
|
||||
pendingNewEntries.add(entry);
|
||||
|
@ -162,7 +188,7 @@ class MediaStoreSource extends CollectionSource {
|
|||
// sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg`
|
||||
@override
|
||||
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');
|
||||
final uriByContentId = Map.fromEntries(changedUris.map((uri) {
|
||||
|
|
|
@ -14,8 +14,8 @@ mixin TagMixin on SourceBase {
|
|||
|
||||
List<String> sortedTags = List.unmodifiable([]);
|
||||
|
||||
Future<void> loadCatalogMetadata() async {
|
||||
final saved = await metadataDb.loadAllMetadataEntries();
|
||||
Future<void> loadCatalogMetadata({Set<int>? ids}) async {
|
||||
final saved = await (ids != null ? metadataDb.loadCatalogMetadataById(ids) : metadataDb.loadCatalogMetadata());
|
||||
final idMap = entryById;
|
||||
saved.forEach((metadata) => idMap[metadata.id]?.catalogMetadata = metadata);
|
||||
onCatalogMetadataChanged();
|
||||
|
@ -42,7 +42,7 @@ mixin TagMixin on SourceBase {
|
|||
if (entry.isCatalogued) {
|
||||
newMetadata.add(entry.catalogMetadata!);
|
||||
if (newMetadata.length >= commitCountThreshold) {
|
||||
await metadataDb.saveMetadata(Set.unmodifiable(newMetadata));
|
||||
await metadataDb.saveCatalogMetadata(Set.unmodifiable(newMetadata));
|
||||
onCatalogMetadataChanged();
|
||||
newMetadata.clear();
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ mixin TagMixin on SourceBase {
|
|||
}
|
||||
setProgress(done: ++progressDone, total: progressTotal);
|
||||
}
|
||||
await metadataDb.saveMetadata(Set.unmodifiable(newMetadata));
|
||||
await metadataDb.saveCatalogMetadata(Set.unmodifiable(newMetadata));
|
||||
onCatalogMetadataChanged();
|
||||
}
|
||||
|
||||
|
|
|
@ -115,8 +115,7 @@ class Analyzer {
|
|||
settings.systemLocalesFallback = await deviceService.getLocales();
|
||||
_l10n = await AppLocalizations.delegate.load(settings.appliedLocale);
|
||||
_serviceStateNotifier.value = AnalyzerState.running;
|
||||
await _source.init();
|
||||
unawaited(_source.refresh(analysisController: _controller));
|
||||
await _source.init(analysisController: _controller);
|
||||
|
||||
_notificationUpdateTimer = Timer.periodic(notificationUpdateInterval, (_) async {
|
||||
if (!isRunning) return;
|
||||
|
|
|
@ -11,7 +11,7 @@ abstract class MediaStoreService {
|
|||
Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById);
|
||||
|
||||
// knownEntries: map of contentId -> dateModifiedSecs
|
||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries);
|
||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory});
|
||||
|
||||
// returns media URI
|
||||
Future<Uri?> scanFile(String path, String mimeType);
|
||||
|
@ -48,11 +48,12 @@ class PlatformMediaStoreService implements MediaStoreService {
|
|||
}
|
||||
|
||||
@override
|
||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries) {
|
||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
|
||||
try {
|
||||
return _streamChannel
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'knownEntries': knownEntries,
|
||||
'directory': directory,
|
||||
})
|
||||
.where((event) => event is Map)
|
||||
.map((event) => AvesEntry.fromMap(event as Map));
|
||||
|
|
|
@ -168,7 +168,16 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
|||
debugPrint('$runtimeType lifecycle ${state.name}');
|
||||
switch (state) {
|
||||
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;
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.detached:
|
||||
|
|
|
@ -96,35 +96,46 @@ class _CollectionGridContent extends StatelessWidget {
|
|||
final scrollableWidth = c.item1;
|
||||
final columnCount = c.item2;
|
||||
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(
|
||||
extent: thumbnailExtent,
|
||||
child: EntryListDetailsTheme(
|
||||
extent: thumbnailExtent,
|
||||
child: SectionedEntryListLayoutProvider(
|
||||
collection: collection,
|
||||
scrollableWidth: scrollableWidth,
|
||||
tileLayout: tileLayout,
|
||||
columnCount: columnCount,
|
||||
spacing: tileSpacing,
|
||||
tileExtent: thumbnailExtent,
|
||||
tileBuilder: (entry) => AnimatedBuilder(
|
||||
animation: favourites,
|
||||
builder: (context, child) {
|
||||
return InteractiveTile(
|
||||
key: ValueKey(entry.id),
|
||||
collection: collection,
|
||||
entry: entry,
|
||||
thumbnailExtent: thumbnailExtent,
|
||||
tileLayout: tileLayout,
|
||||
isScrollingNotifier: _isScrollingNotifier,
|
||||
);
|
||||
},
|
||||
),
|
||||
tileAnimationDelay: tileAnimationDelay,
|
||||
child: child!,
|
||||
child: ValueListenableBuilder<SourceState>(
|
||||
valueListenable: collection.source.stateNotifier,
|
||||
builder: (context, sourceState, child) {
|
||||
late final Duration tileAnimationDelay;
|
||||
if (sourceState == SourceState.ready) {
|
||||
// do not listen for animation delay change
|
||||
final target = context.read<DurationsData>().staggeredAnimationPageTarget;
|
||||
tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(target);
|
||||
} else {
|
||||
tileAnimationDelay = Duration.zero;
|
||||
}
|
||||
return SectionedEntryListLayoutProvider(
|
||||
collection: collection,
|
||||
scrollableWidth: scrollableWidth,
|
||||
tileLayout: tileLayout,
|
||||
columnCount: columnCount,
|
||||
spacing: tileSpacing,
|
||||
tileExtent: thumbnailExtent,
|
||||
tileBuilder: (entry) => AnimatedBuilder(
|
||||
animation: favourites,
|
||||
builder: (context, child) {
|
||||
return InteractiveTile(
|
||||
key: ValueKey(entry.id),
|
||||
collection: collection,
|
||||
entry: entry,
|
||||
thumbnailExtent: thumbnailExtent,
|
||||
tileLayout: tileLayout,
|
||||
isScrollingNotifier: _isScrollingNotifier,
|
||||
);
|
||||
},
|
||||
),
|
||||
tileAnimationDelay: tileAnimationDelay,
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -51,10 +51,9 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
}
|
||||
|
||||
final source = context.read<CollectionSource>();
|
||||
if (!source.initialized) {
|
||||
if (source.initState != SourceInitializationState.full) {
|
||||
// source may be uninitialized in viewer mode
|
||||
await source.init();
|
||||
unawaited(source.refresh());
|
||||
}
|
||||
|
||||
final entriesByDestination = <String, Set<AvesEntry>>{};
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:aves/model/settings/settings.dart';
|
|||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/services/analysis_service.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/identity/aves_expansion_tile.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'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await source.init();
|
||||
await source.refresh();
|
||||
},
|
||||
child: const Text('Source full refresh'),
|
||||
onPressed: () => source.init(loadTopEntriesFirst: false),
|
||||
child: const Text('Source refresh (top off)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
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(
|
||||
onPressed: () => AnalysisService.startService(force: false),
|
||||
|
|
|
@ -21,7 +21,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
late Future<int> _dbFileSizeLoader;
|
||||
late Future<Set<AvesEntry>> _dbEntryLoader;
|
||||
late Future<Map<int?, int?>> _dbDateLoader;
|
||||
late Future<List<CatalogMetadata>> _dbMetadataLoader;
|
||||
late Future<Set<CatalogMetadata>> _dbMetadataLoader;
|
||||
late Future<Set<AddressDetails>> _dbAddressLoader;
|
||||
late Future<Set<TrashDetails>> _dbTrashLoader;
|
||||
late Future<Set<FavouriteRow>> _dbFavouritesLoader;
|
||||
|
@ -108,7 +108,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
);
|
||||
},
|
||||
),
|
||||
FutureBuilder<List>(
|
||||
FutureBuilder<Set>(
|
||||
future: _dbMetadataLoader,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
||||
|
@ -122,7 +122,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => metadataDb.clearMetadataEntries().then((_) => _startDbReport()),
|
||||
onPressed: () => metadataDb.clearCatalogMetadata().then((_) => _startDbReport()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -243,10 +243,10 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
|
||||
void _startDbReport() {
|
||||
_dbFileSizeLoader = metadataDb.dbFileSize();
|
||||
_dbEntryLoader = metadataDb.loadAllEntries();
|
||||
_dbEntryLoader = metadataDb.loadEntries();
|
||||
_dbDateLoader = metadataDb.loadDates();
|
||||
_dbMetadataLoader = metadataDb.loadAllMetadataEntries();
|
||||
_dbAddressLoader = metadataDb.loadAllAddresses();
|
||||
_dbMetadataLoader = metadataDb.loadCatalogMetadata();
|
||||
_dbAddressLoader = metadataDb.loadAddresses();
|
||||
_dbTrashLoader = metadataDb.loadAllTrashDetails();
|
||||
_dbFavouritesLoader = metadataDb.loadAllFavourites();
|
||||
_dbCoversLoader = metadataDb.loadAllCovers();
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/app_mode.dart';
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/album.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/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
|
@ -116,14 +117,33 @@ class _HomePageState extends State<HomePage> {
|
|||
}
|
||||
context.read<ValueNotifier<AppMode>>().value = appMode;
|
||||
unawaited(reportService.setCustomKey('app_mode', appMode.toString()));
|
||||
debugPrint('Storage check complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
||||
|
||||
if (appMode != AppMode.view || _isViewerSourceable(_viewerEntry!)) {
|
||||
debugPrint('Storage check complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
||||
unawaited(GlobalSearch.registerCallback());
|
||||
unawaited(AnalysisService.registerCallback());
|
||||
final source = context.read<CollectionSource>();
|
||||
await source.init();
|
||||
unawaited(source.refresh());
|
||||
switch (appMode) {
|
||||
case AppMode.main:
|
||||
case AppMode.pickMediaExternal:
|
||||
unawaited(GlobalSearch.registerCallback());
|
||||
unawaited(AnalysisService.registerCallback());
|
||||
final source = context.read<CollectionSource>();
|
||||
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
|
||||
|
@ -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 {
|
||||
if (uri.startsWith('/')) {
|
||||
|
@ -156,7 +178,7 @@ class _HomePageState extends State<HomePage> {
|
|||
CollectionLens? collection;
|
||||
|
||||
final source = context.read<CollectionSource>();
|
||||
if (source.initialized) {
|
||||
if (source.initState != SourceInitializationState.none) {
|
||||
final album = viewerEntry.directory;
|
||||
if (album != null) {
|
||||
// wait for collection to pass the `loading` state
|
||||
|
@ -174,6 +196,11 @@ class _HomePageState extends State<HomePage> {
|
|||
collection = CollectionLens(
|
||||
source: source,
|
||||
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 collectionEntry = collection.sortedEntries.firstWhereOrNull((entry) => entry.path == viewerEntryPath);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/coordinate.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
|
@ -371,6 +372,9 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
}
|
||||
|
||||
void _goToCollection(CollectionFilter filter) {
|
||||
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
|
||||
if (!isMainMode) return;
|
||||
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
|
|
|
@ -188,7 +188,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
showFeedback(context, l10n.genericFailureFeedback);
|
||||
} else {
|
||||
final source = context.read<CollectionSource>();
|
||||
if (source.initialized) {
|
||||
if (source.initState != SourceInitializationState.none) {
|
||||
await source.removeEntries({entry.uri}, includeTrash: true);
|
||||
}
|
||||
EntryRemovedNotification(entry).dispatch(context);
|
||||
|
@ -203,9 +203,8 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
if (options == null) return;
|
||||
|
||||
final source = context.read<CollectionSource>();
|
||||
if (!source.initialized) {
|
||||
if (source.initState != SourceInitializationState.full) {
|
||||
await source.init();
|
||||
unawaited(source.refresh());
|
||||
}
|
||||
final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export);
|
||||
if (destinationAlbum == null) return;
|
||||
|
|
|
@ -39,9 +39,9 @@ class _DbTabState extends State<DbTab> {
|
|||
void _loadDatabase() {
|
||||
final id = entry.id;
|
||||
_dbDateLoader = metadataDb.loadDates().then((values) => values[id]);
|
||||
_dbEntryLoader = metadataDb.loadAllEntries().then((values) => values.firstWhereOrNull((row) => row.id == id));
|
||||
_dbMetadataLoader = metadataDb.loadAllMetadataEntries().then((values) => values.firstWhereOrNull((row) => row.id == id));
|
||||
_dbAddressLoader = metadataDb.loadAllAddresses().then((values) => values.firstWhereOrNull((row) => row.id == id));
|
||||
_dbEntryLoader = metadataDb.loadEntries().then((values) => values.firstWhereOrNull((row) => row.id == id));
|
||||
_dbMetadataLoader = metadataDb.loadCatalogMetadata().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));
|
||||
_dbVideoPlaybackLoader = metadataDb.loadVideoPlayback(id);
|
||||
setState(() {});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/device.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
|
@ -399,8 +400,12 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
|||
}
|
||||
|
||||
void _goToCollection(CollectionFilter filter) {
|
||||
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
|
||||
if (!isMainMode) return;
|
||||
|
||||
final baseCollection = collection;
|
||||
if (baseCollection == null) return;
|
||||
|
||||
_onLeave();
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/actions/entry_info_actions.dart';
|
||||
import 'package:aves/model/actions/events.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
|
@ -199,7 +198,7 @@ class _InfoPageContentState extends State<_InfoPageContent> {
|
|||
collection: collection,
|
||||
actionDelegate: _actionDelegate,
|
||||
isEditingMetadataNotifier: _isEditingMetadataNotifier,
|
||||
onFilter: _goToCollection,
|
||||
onFilter: _onFilter,
|
||||
);
|
||||
final locationAtTop = widget.split && entry.hasGps;
|
||||
final locationSection = LocationSection(
|
||||
|
@ -207,7 +206,7 @@ class _InfoPageContentState extends State<_InfoPageContent> {
|
|||
entry: entry,
|
||||
showTitle: !locationAtTop,
|
||||
isScrollingNotifier: widget.isScrollingNotifier,
|
||||
onFilter: _goToCollection,
|
||||
onFilter: _onFilter,
|
||||
);
|
||||
final basicAndLocationSliver = locationAtTop
|
||||
? SliverToBoxAdapter(
|
||||
|
@ -265,9 +264,5 @@ class _InfoPageContentState extends State<_InfoPageContent> {
|
|||
});
|
||||
}
|
||||
|
||||
void _goToCollection(CollectionFilter filter) {
|
||||
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
|
||||
if (!isMainMode || collection == null) return;
|
||||
FilterSelectedNotification(filter).dispatch(context);
|
||||
}
|
||||
void _onFilter(CollectionFilter filter) => FilterSelectedNotification(filter).dispatch(context);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
|
|||
Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById) => SynchronousFuture([]);
|
||||
|
||||
@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;
|
||||
|
||||
|
|
|
@ -19,12 +19,12 @@ class FakeMetadataDb extends Fake implements MetadataDb {
|
|||
Future<void> init() => SynchronousFuture(null);
|
||||
|
||||
@override
|
||||
Future<void> removeIds(Set<int> ids, {Set<EntryDataType>? dataTypes}) => SynchronousFuture(null);
|
||||
Future<void> removeIds(Iterable<int> ids, {Set<EntryDataType>? dataTypes}) => SynchronousFuture(null);
|
||||
|
||||
// entries
|
||||
|
||||
@override
|
||||
Future<Set<AvesEntry>> loadAllEntries() => SynchronousFuture({});
|
||||
Future<Set<AvesEntry>> loadEntries({String? directory}) => SynchronousFuture({});
|
||||
|
||||
@override
|
||||
Future<void> saveEntries(Iterable<AvesEntry> entries) => SynchronousFuture(null);
|
||||
|
@ -40,18 +40,18 @@ class FakeMetadataDb extends Fake implements MetadataDb {
|
|||
// catalog metadata
|
||||
|
||||
@override
|
||||
Future<List<CatalogMetadata>> loadAllMetadataEntries() => SynchronousFuture([]);
|
||||
Future<Set<CatalogMetadata>> loadCatalogMetadata() => SynchronousFuture({});
|
||||
|
||||
@override
|
||||
Future<void> saveMetadata(Set<CatalogMetadata> metadataEntries) => SynchronousFuture(null);
|
||||
Future<void> saveCatalogMetadata(Set<CatalogMetadata> metadataEntries) => SynchronousFuture(null);
|
||||
|
||||
@override
|
||||
Future<void> updateMetadata(int id, CatalogMetadata? metadata) => SynchronousFuture(null);
|
||||
Future<void> updateCatalogMetadata(int id, CatalogMetadata? metadata) => SynchronousFuture(null);
|
||||
|
||||
// address
|
||||
|
||||
@override
|
||||
Future<Set<AddressDetails>> loadAllAddresses() => SynchronousFuture({});
|
||||
Future<Set<AddressDetails>> loadAddresses() => SynchronousFuture({});
|
||||
|
||||
@override
|
||||
Future<void> saveAddresses(Set<AddressDetails> addresses) => SynchronousFuture(null);
|
||||
|
@ -101,5 +101,5 @@ class FakeMetadataDb extends Fake implements MetadataDb {
|
|||
// video playback
|
||||
|
||||
@override
|
||||
Future<void> removeVideoPlayback(Set<int> ids) => SynchronousFuture(null);
|
||||
Future<void> removeVideoPlayback(Iterable<int> ids) => SynchronousFuture(null);
|
||||
}
|
||||
|
|
|
@ -83,7 +83,6 @@ void main() {
|
|||
}
|
||||
});
|
||||
await source.init();
|
||||
await source.refresh();
|
||||
await readyCompleter.future;
|
||||
return source;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue