import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; final Favourites favourites = Favourites._private(); class Favourites with ChangeNotifier { Set _rows = {}; Favourites._private(); Future init() async { _rows = await metadataDb.loadAllFavourites(); } int get count => _rows.length; Set get all => Set.unmodifiable(_rows.map((v) => v.entryId)); bool isFavourite(AvesEntry entry) => _rows.any((row) => row.entryId == entry.id); FavouriteRow _entryToRow(AvesEntry entry) => FavouriteRow(entryId: entry.id); Future add(Set entries) async { final newRows = entries.map(_entryToRow).toSet(); await metadataDb.addFavourites(newRows); _rows.addAll(newRows); notifyListeners(); } Future removeEntries(Set entries) => removeIds(entries.map((entry) => entry.id).toSet()); Future removeIds(Set entryIds) async { final removedRows = _rows.where((row) => entryIds.contains(row.entryId)).toSet(); await metadataDb.removeFavourites(removedRows); removedRows.forEach(_rows.remove); notifyListeners(); } Future clear() async { await metadataDb.clearFavourites(); _rows.clear(); notifyListeners(); } // import/export Map>? export(CollectionSource source) { final visibleEntries = source.visibleEntries; final ids = favourites.all; final paths = visibleEntries.where((entry) => ids.contains(entry.id)).map((entry) => entry.path).whereNotNull().toSet(); final byVolume = groupBy(paths, androidFileUtils.getStorageVolume); final jsonMap = Map.fromEntries(byVolume.entries.map((kv) { final volume = kv.key?.path; if (volume == null) return null; final rootLength = volume.length; final relativePaths = kv.value.map((v) => v.substring(rootLength)).toList(); return MapEntry(volume, relativePaths); }).whereNotNull()); return jsonMap.isNotEmpty ? jsonMap : null; } void import(dynamic jsonMap, CollectionSource source) { if (jsonMap is! Map) { debugPrint('failed to import favourites for jsonMap=$jsonMap'); return; } final visibleEntries = source.visibleEntries; final foundEntries = {}; final missedPaths = {}; jsonMap.forEach((volume, relativePaths) { if (volume is String && relativePaths is List) { relativePaths.forEach((relativePath) { final path = pContext.join(volume, relativePath); final entry = visibleEntries.firstWhereOrNull((entry) => entry.path == path); if (entry != null) { foundEntries.add(entry); } else { missedPaths.add(path); } }); } else { debugPrint('failed to import favourites for volume=$volume, relativePaths=${relativePaths.runtimeType}'); } if (foundEntries.isNotEmpty) { favourites.add(foundEntries); } if (missedPaths.isNotEmpty) { debugPrint('failed to import favourites with ${missedPaths.length} missed paths'); } }); } } @immutable class FavouriteRow extends Equatable { final int entryId; @override List get props => [entryId]; const FavouriteRow({ required this.entryId, }); factory FavouriteRow.fromMap(Map map) { return FavouriteRow( entryId: map['id'] as int, ); } Map toMap() => { 'id': entryId, }; }