parent
ca991ae9dd
commit
c9041c9beb
43 changed files with 425 additions and 102 deletions
|
@ -354,6 +354,10 @@
|
|||
"settingsActionExport": "Exportieren",
|
||||
"settingsActionImport": "Importieren",
|
||||
|
||||
"appExportCovers": "Titelbilder",
|
||||
"appExportFavourites": "Favoriten",
|
||||
"appExportSettings": "Einstellungen",
|
||||
|
||||
"settingsSectionNavigation": "Navigation",
|
||||
"settingsHome": "Startseite",
|
||||
"settingsKeepScreenOnTile": "Bildschirm eingeschaltet lassen",
|
||||
|
|
|
@ -523,6 +523,10 @@
|
|||
"settingsActionExport": "Export",
|
||||
"settingsActionImport": "Import",
|
||||
|
||||
"appExportCovers": "Covers",
|
||||
"appExportFavourites": "Favourites",
|
||||
"appExportSettings": "Settings",
|
||||
|
||||
"settingsSectionNavigation": "Navigation",
|
||||
"settingsHome": "Home",
|
||||
"settingsKeepScreenOnTile": "Keep screen on",
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
"chipActionPin": "Fijar",
|
||||
"chipActionUnpin": "Dejar de fijar",
|
||||
"chipActionRename": "Renombrar",
|
||||
"chipActionSetCover": "Elegir portada",
|
||||
"chipActionSetCover": "Elegir carátula",
|
||||
"chipActionCreateAlbum": "Crear álbum",
|
||||
|
||||
"entryActionCopyToClipboard": "Copiar al portapapeles",
|
||||
|
@ -355,6 +355,10 @@
|
|||
"settingsActionExport": "Exportar",
|
||||
"settingsActionImport": "Importar",
|
||||
|
||||
"appExportCovers": "Carátulas",
|
||||
"appExportFavourites": "Favoritos",
|
||||
"appExportSettings": "Ajustes",
|
||||
|
||||
"settingsSectionNavigation": "Navegación",
|
||||
"settingsHome": "Inicio",
|
||||
"settingsKeepScreenOnTile": "Mantener pantalla encendida",
|
||||
|
|
|
@ -354,6 +354,10 @@
|
|||
"settingsActionExport": "Exporter",
|
||||
"settingsActionImport": "Importer",
|
||||
|
||||
"appExportCovers": "Couvertures",
|
||||
"appExportFavourites": "Favoris",
|
||||
"appExportSettings": "Réglages",
|
||||
|
||||
"settingsSectionNavigation": "Navigation",
|
||||
"settingsHome": "Page d’accueil",
|
||||
"settingsKeepScreenOnTile": "Maintenir l’écran allumé",
|
||||
|
|
|
@ -354,6 +354,10 @@
|
|||
"settingsActionExport": "내보내기",
|
||||
"settingsActionImport": "가져오기",
|
||||
|
||||
"appExportCovers": "대표 이미지",
|
||||
"appExportFavourites": "즐겨찾기",
|
||||
"appExportSettings": "설정",
|
||||
|
||||
"settingsSectionNavigation": "탐색",
|
||||
"settingsHome": "홈",
|
||||
"settingsKeepScreenOnTile": "화면 자동 꺼짐 방지",
|
||||
|
|
|
@ -354,6 +354,10 @@
|
|||
"settingsActionExport": "Exportar",
|
||||
"settingsActionImport": "Importar",
|
||||
|
||||
"appExportCovers": "Capas",
|
||||
"appExportFavourites": "Favoritos",
|
||||
"appExportSettings": "Configurações",
|
||||
|
||||
"settingsSectionNavigation": "Navegação",
|
||||
"settingsHome": "Início",
|
||||
"settingsKeepScreenOnTile": "Manter a tela ligada",
|
||||
|
|
|
@ -354,6 +354,9 @@
|
|||
"settingsActionExport": "Экспорт",
|
||||
"settingsActionImport": "Импорт",
|
||||
|
||||
"appExportFavourites": "Избранное",
|
||||
"appExportSettings": "Настройки",
|
||||
|
||||
"settingsSectionNavigation": "Навигация",
|
||||
"settingsHome": "Домашний каталог",
|
||||
"settingsKeepScreenOnTile": "Держать экран включенным",
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/filters.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:collection/collection.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
final Covers covers = Covers._private();
|
||||
|
||||
|
@ -20,6 +21,8 @@ class Covers with ChangeNotifier {
|
|||
|
||||
int get count => _rows.length;
|
||||
|
||||
Set<CoverRow> get all => Set.unmodifiable(_rows);
|
||||
|
||||
int? coverContentId(CollectionFilter filter) => _rows.firstWhereOrNull((row) => row.filter == filter)?.contentId;
|
||||
|
||||
Future<void> set(CollectionFilter filter, int? contentId) async {
|
||||
|
@ -75,6 +78,61 @@ class Covers with ChangeNotifier {
|
|||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// import/export
|
||||
|
||||
List<Map<String, dynamic>>? export(CollectionSource source) {
|
||||
final visibleEntries = source.visibleEntries;
|
||||
final jsonList = covers.all
|
||||
.map((row) {
|
||||
final id = row.contentId;
|
||||
final path = visibleEntries.firstWhereOrNull((entry) => id == entry.contentId)?.path;
|
||||
if (path == null) return null;
|
||||
|
||||
final volume = androidFileUtils.getStorageVolume(path)?.path;
|
||||
if (volume == null) return null;
|
||||
|
||||
final relativePath = path.substring(volume.length);
|
||||
return {
|
||||
'filter': row.filter.toJson(),
|
||||
'volume': volume,
|
||||
'relativePath': relativePath,
|
||||
};
|
||||
})
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
return jsonList.isNotEmpty ? jsonList : null;
|
||||
}
|
||||
|
||||
void import(dynamic jsonList, CollectionSource source) {
|
||||
if (jsonList is! List) {
|
||||
debugPrint('failed to import covers for jsonMap=$jsonList');
|
||||
return;
|
||||
}
|
||||
|
||||
final visibleEntries = source.visibleEntries;
|
||||
jsonList.forEach((row) {
|
||||
final filter = CollectionFilter.fromJson(row['filter']);
|
||||
if (filter == null) {
|
||||
debugPrint('failed to import cover for row=$row');
|
||||
return;
|
||||
}
|
||||
|
||||
final volume = row['volume'];
|
||||
final relativePath = row['relativePath'];
|
||||
if (volume is String && relativePath is String) {
|
||||
final path = pContext.join(volume, relativePath);
|
||||
final entry = visibleEntries.firstWhereOrNull((entry) => entry.path == path && filter.test(entry));
|
||||
if (entry != null) {
|
||||
covers.set(filter, entry.contentId);
|
||||
} else {
|
||||
debugPrint('failed to import cover for path=$path, filter=$filter');
|
||||
}
|
||||
} else {
|
||||
debugPrint('failed to import cover for volume=$volume, relativePath=$relativePath, filter=$filter');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
|
|
|
@ -57,7 +57,7 @@ extension ExtraAvesEntryImages on AvesEntry {
|
|||
|
||||
bool _isReady(Object providerKey) => imageCache!.statusForKey(providerKey).keepAlive;
|
||||
|
||||
List<ThumbnailProvider> get cachedThumbnails => EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).where(_isReady).map((key) => ThumbnailProvider(key)).toList();
|
||||
List<ThumbnailProvider> get cachedThumbnails => EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).where(_isReady).map(ThumbnailProvider.new).toList();
|
||||
|
||||
ThumbnailProvider get bestCachedThumbnail {
|
||||
final sizedThumbnailKey = EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).firstWhereOrNull(_isReady);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:aves/model/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:collection/collection.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -17,6 +19,8 @@ class Favourites with ChangeNotifier {
|
|||
|
||||
int get count => _rows.length;
|
||||
|
||||
Set<int> get all => Set.unmodifiable(_rows.map((v) => v.contentId));
|
||||
|
||||
bool isFavourite(AvesEntry entry) => _rows.any((row) => row.contentId == entry.contentId);
|
||||
|
||||
FavouriteRow _entryToRow(AvesEntry entry) => FavouriteRow(contentId: entry.contentId!, path: entry.path!);
|
||||
|
@ -59,6 +63,56 @@ class Favourites with ChangeNotifier {
|
|||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// import/export
|
||||
|
||||
Map<String, List<String>>? export(CollectionSource source) {
|
||||
final visibleEntries = source.visibleEntries;
|
||||
final ids = favourites.all;
|
||||
final paths = visibleEntries.where((entry) => ids.contains(entry.contentId)).map((entry) => entry.path).whereNotNull().toSet();
|
||||
final byVolume = groupBy<String, StorageVolume?>(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 = <AvesEntry>{};
|
||||
final missedPaths = <String>{};
|
||||
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
|
||||
|
|
|
@ -231,7 +231,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
Future<Set<AvesEntry>> loadAllEntries() async {
|
||||
final db = await _database;
|
||||
final maps = await db.query(entryTable);
|
||||
final entries = maps.map((map) => AvesEntry.fromMap(map)).toSet();
|
||||
final entries = maps.map(AvesEntry.fromMap).toSet();
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
@ -273,7 +273,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
orderBy: 'sourceDateTakenMillis DESC',
|
||||
limit: limit,
|
||||
);
|
||||
return maps.map((map) => AvesEntry.fromMap(map)).toSet();
|
||||
return maps.map(AvesEntry.fromMap).toSet();
|
||||
}
|
||||
|
||||
// date taken
|
||||
|
@ -306,7 +306,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
Future<List<CatalogMetadata>> loadAllMetadataEntries() async {
|
||||
final db = await _database;
|
||||
final maps = await db.query(metadataTable);
|
||||
final metadataEntries = maps.map((map) => CatalogMetadata.fromMap(map)).toList();
|
||||
final metadataEntries = maps.map(CatalogMetadata.fromMap).toList();
|
||||
return metadataEntries;
|
||||
}
|
||||
|
||||
|
@ -367,7 +367,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
Future<List<AddressDetails>> loadAllAddresses() async {
|
||||
final db = await _database;
|
||||
final maps = await db.query(addressTable);
|
||||
final addresses = maps.map((map) => AddressDetails.fromMap(map)).toList();
|
||||
final addresses = maps.map(AddressDetails.fromMap).toList();
|
||||
return addresses;
|
||||
}
|
||||
|
||||
|
@ -413,7 +413,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
Future<Set<FavouriteRow>> loadAllFavourites() async {
|
||||
final db = await _database;
|
||||
final maps = await db.query(favouriteTable);
|
||||
final rows = maps.map((map) => FavouriteRow.fromMap(map)).toSet();
|
||||
final rows = maps.map(FavouriteRow.fromMap).toSet();
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ class MultiPageInfo {
|
|||
factory MultiPageInfo.fromPageMaps(AvesEntry mainEntry, List<Map> pageMaps) {
|
||||
return MultiPageInfo(
|
||||
mainEntry: mainEntry,
|
||||
pages: pageMaps.map((page) => SinglePageInfo.fromMap(page)).toList(),
|
||||
pages: pageMaps.map(SinglePageInfo.fromMap).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:aves/l10n/l10n.dart';
|
||||
|
@ -570,12 +569,11 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
// import/export
|
||||
|
||||
String toJson() => jsonEncode(Map.fromEntries(
|
||||
Map<String, dynamic> export() => Map.fromEntries(
|
||||
_prefs!.getKeys().whereNot(internalKeys.contains).map((k) => MapEntry(k, _prefs!.get(k))),
|
||||
));
|
||||
);
|
||||
|
||||
Future<void> fromJson(String jsonString) async {
|
||||
final jsonMap = jsonDecode(jsonString);
|
||||
Future<void> import(dynamic jsonMap) async {
|
||||
if (jsonMap is Map<String, dynamic>) {
|
||||
// clear to restore defaults
|
||||
await reset(includeInternalKeys: false);
|
||||
|
|
|
@ -8,4 +8,4 @@ enum EntrySortFactor { date, name, rating, size }
|
|||
|
||||
enum EntryGroupFactor { none, album, month, day }
|
||||
|
||||
enum TileLayout { grid, list }
|
||||
enum TileLayout { grid, list }
|
||||
|
|
|
@ -3,4 +3,4 @@ class IPTC {
|
|||
|
||||
// ApplicationRecord tags
|
||||
static const int keywordsTag = 25;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
Future<Set<Package>> getPackages() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getPackages');
|
||||
final packages = (result as List).cast<Map>().map((map) => Package.fromMap(map)).toSet();
|
||||
final packages = (result as List).cast<Map>().map(Package.fromMap).toSet();
|
||||
// additional info for known directories
|
||||
final kakaoTalk = packages.firstWhereOrNull((package) => package.packageName == 'com.kakao.talk');
|
||||
if (kakaoTalk != null) {
|
||||
|
|
|
@ -66,7 +66,7 @@ class ServicePolicy {
|
|||
}
|
||||
}
|
||||
|
||||
LinkedHashMap<Object, _Task> _getQueue(int priority) => _queues.putIfAbsent(priority, () => LinkedHashMap());
|
||||
LinkedHashMap<Object, _Task> _getQueue(int priority) => _queues.putIfAbsent(priority, LinkedHashMap.new);
|
||||
|
||||
void _pickNext() {
|
||||
_notifyQueueState();
|
||||
|
|
|
@ -32,18 +32,18 @@ final StorageService storageService = getIt<StorageService>();
|
|||
final WindowService windowService = getIt<WindowService>();
|
||||
|
||||
void initPlatformServices() {
|
||||
getIt.registerLazySingleton<p.Context>(() => p.Context());
|
||||
getIt.registerLazySingleton<AvesAvailability>(() => LiveAvesAvailability());
|
||||
getIt.registerLazySingleton<MetadataDb>(() => SqfliteMetadataDb());
|
||||
getIt.registerLazySingleton<p.Context>(p.Context.new);
|
||||
getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new);
|
||||
getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new);
|
||||
|
||||
getIt.registerLazySingleton<AndroidAppService>(() => PlatformAndroidAppService());
|
||||
getIt.registerLazySingleton<DeviceService>(() => PlatformDeviceService());
|
||||
getIt.registerLazySingleton<EmbeddedDataService>(() => PlatformEmbeddedDataService());
|
||||
getIt.registerLazySingleton<MediaFileService>(() => PlatformMediaFileService());
|
||||
getIt.registerLazySingleton<MediaStoreService>(() => PlatformMediaStoreService());
|
||||
getIt.registerLazySingleton<MetadataEditService>(() => PlatformMetadataEditService());
|
||||
getIt.registerLazySingleton<MetadataFetchService>(() => PlatformMetadataFetchService());
|
||||
getIt.registerLazySingleton<ReportService>(() => PlatformReportService());
|
||||
getIt.registerLazySingleton<StorageService>(() => PlatformStorageService());
|
||||
getIt.registerLazySingleton<WindowService>(() => PlatformWindowService());
|
||||
getIt.registerLazySingleton<AndroidAppService>(PlatformAndroidAppService.new);
|
||||
getIt.registerLazySingleton<DeviceService>(PlatformDeviceService.new);
|
||||
getIt.registerLazySingleton<EmbeddedDataService>(PlatformEmbeddedDataService.new);
|
||||
getIt.registerLazySingleton<MediaFileService>(PlatformMediaFileService.new);
|
||||
getIt.registerLazySingleton<MediaStoreService>(PlatformMediaStoreService.new);
|
||||
getIt.registerLazySingleton<MetadataEditService>(PlatformMetadataEditService.new);
|
||||
getIt.registerLazySingleton<MetadataFetchService>(PlatformMetadataFetchService.new);
|
||||
getIt.registerLazySingleton<ReportService>(PlatformReportService.new);
|
||||
getIt.registerLazySingleton<StorageService>(PlatformStorageService.new);
|
||||
getIt.registerLazySingleton<WindowService>(PlatformWindowService.new);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class GeocodingService {
|
|||
// returns nothing with `maxResults` of 1, but succeeds with `maxResults` of 2+
|
||||
'maxResults': 2,
|
||||
});
|
||||
return (result as List).cast<Map>().map((map) => Address.fromMap(map)).toList();
|
||||
return (result as List).cast<Map>().map(Address.fromMap).toList();
|
||||
} on PlatformException catch (e, stack) {
|
||||
if (e.code != 'getAddress-empty' && e.code != 'getAddress-network') {
|
||||
await reportService.recordError(e, stack);
|
||||
|
|
|
@ -325,11 +325,14 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required Iterable<AvesEntry> entries,
|
||||
}) {
|
||||
try {
|
||||
return _opStreamChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'delete',
|
||||
'id': opId,
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
}).map((event) => ImageOpEvent.fromMap(event));
|
||||
return _opStreamChannel
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'delete',
|
||||
'id': opId,
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
})
|
||||
.where((event) => event is Map)
|
||||
.map((event) => ImageOpEvent.fromMap(event as Map));
|
||||
} on PlatformException catch (e, stack) {
|
||||
reportService.recordError(e, stack);
|
||||
return Stream.error(e);
|
||||
|
@ -345,14 +348,17 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required NameConflictStrategy nameConflictStrategy,
|
||||
}) {
|
||||
try {
|
||||
return _opStreamChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'move',
|
||||
'id': opId,
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
'copy': copy,
|
||||
'destinationPath': destinationAlbum,
|
||||
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
|
||||
}).map((event) => MoveOpEvent.fromMap(event));
|
||||
return _opStreamChannel
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'move',
|
||||
'id': opId,
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
'copy': copy,
|
||||
'destinationPath': destinationAlbum,
|
||||
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
|
||||
})
|
||||
.where((event) => event is Map)
|
||||
.map((event) => MoveOpEvent.fromMap(event as Map));
|
||||
} on PlatformException catch (e, stack) {
|
||||
reportService.recordError(e, stack);
|
||||
return Stream.error(e);
|
||||
|
@ -367,13 +373,16 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required NameConflictStrategy nameConflictStrategy,
|
||||
}) {
|
||||
try {
|
||||
return _opStreamChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'export',
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
'mimeType': mimeType,
|
||||
'destinationPath': destinationAlbum,
|
||||
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
|
||||
}).map((event) => ExportOpEvent.fromMap(event));
|
||||
return _opStreamChannel
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'export',
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
'mimeType': mimeType,
|
||||
'destinationPath': destinationAlbum,
|
||||
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
|
||||
})
|
||||
.where((event) => event is Map)
|
||||
.map((event) => ExportOpEvent.fromMap(event as Map));
|
||||
} on PlatformException catch (e, stack) {
|
||||
reportService.recordError(e, stack);
|
||||
return Stream.error(e);
|
||||
|
@ -386,11 +395,14 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required String newName,
|
||||
}) {
|
||||
try {
|
||||
return _opStreamChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'rename',
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
'newName': newName,
|
||||
}).map((event) => MoveOpEvent.fromMap(event));
|
||||
return _opStreamChannel
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'rename',
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
'newName': newName,
|
||||
})
|
||||
.where((event) => event is Map)
|
||||
.map((event) => MoveOpEvent.fromMap(event as Map));
|
||||
} on PlatformException catch (e, stack) {
|
||||
reportService.recordError(e, stack);
|
||||
return Stream.error(e);
|
||||
|
|
|
@ -50,9 +50,12 @@ class PlatformMediaStoreService implements MediaStoreService {
|
|||
@override
|
||||
Stream<AvesEntry> getEntries(Map<int, int> knownEntries) {
|
||||
try {
|
||||
return _streamChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
'knownEntries': knownEntries,
|
||||
}).map((event) => AvesEntry.fromMap(event));
|
||||
return _streamChannel
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'knownEntries': knownEntries,
|
||||
})
|
||||
.where((event) => event is Map)
|
||||
.map((event) => AvesEntry.fromMap(event as Map));
|
||||
} on PlatformException catch (e, stack) {
|
||||
reportService.recordError(e, stack);
|
||||
return Stream.error(e);
|
||||
|
|
|
@ -47,7 +47,7 @@ class PlatformStorageService implements StorageService {
|
|||
Future<Set<StorageVolume>> getStorageVolumes() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getStorageVolumes');
|
||||
return (result as List).cast<Map>().map((map) => StorageVolume.fromMap(map)).toSet();
|
||||
return (result as List).cast<Map>().map(StorageVolume.fromMap).toSet();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class MonthSectionHeader<T> extends StatelessWidget {
|
|||
if (date == null) return l10n.sectionUnknown;
|
||||
if (date.isThisMonth) return l10n.dateThisMonth;
|
||||
final locale = l10n.localeName;
|
||||
final localized = date.isThisYear? DateFormat.MMMM(locale).format(date) : DateFormat.yMMMM(locale).format(date);
|
||||
final localized = date.isThisYear ? DateFormat.MMMM(locale).format(date) : DateFormat.yMMMM(locale).format(date);
|
||||
return '${localized.substring(0, 1).toUpperCase()}${localized.substring(1)}';
|
||||
}
|
||||
|
||||
|
|
|
@ -320,7 +320,7 @@ class _OverlayCoordinateFilterChipState extends State<_OverlayCoordinateFilterCh
|
|||
filter: filter,
|
||||
useFilterColor: false,
|
||||
maxWidth: double.infinity,
|
||||
onTap: (filter) => FilterSelectedNotification(CoordinateFilter(bounds.sw, bounds.ne) ).dispatch(context),
|
||||
onTap: (filter) => FilterSelectedNotification(CoordinateFilter(bounds.sw, bounds.ne)).dispatch(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -293,7 +293,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
// node size: 64 by default, higher means faster indexing but slower search
|
||||
nodeSize: nodeSize,
|
||||
points: markers,
|
||||
createCluster: (base, lng, lat) => GeoEntry.createCluster(base, lng, lat),
|
||||
createCluster: GeoEntry.createCluster,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'package:aves/ref/mime_types.dart';
|
|||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class MediaStoreScanDirDialog extends StatefulWidget {
|
||||
const MediaStoreScanDirDialog({Key? key}) : super(key: key);
|
||||
|
@ -37,7 +36,7 @@ class _MediaStoreScanDirDialogState extends State<MediaStoreScanDirDialog> {
|
|||
setState(() => _processing = true);
|
||||
await Future.forEach<FileSystemEntity>(Directory(dir).listSync(recursive: true), (file) async {
|
||||
if (file is File) {
|
||||
final mimeType = MimeTypes.forExtension(p.extension(file.path));
|
||||
final mimeType = MimeTypes.forExtension(pContext.extension(file.path));
|
||||
await mediaStoreService.scanFile(file.path, mimeType!);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -57,7 +57,7 @@ class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog<T>> {
|
|||
if (needConfirmation)
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, _selectedValue),
|
||||
child: Text(confirmationButtonLabel!),
|
||||
child: Text(confirmationButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -77,8 +77,8 @@ class _TagEditorPageState extends State<TagEditorPage> {
|
|||
builder: (context, value, child) {
|
||||
final upQuery = value.text.trim().toUpperCase();
|
||||
bool containQuery(String s) => s.toUpperCase().contains(upQuery);
|
||||
final recentFilters = _recentTags.where(containQuery).map((v) => TagFilter(v)).toList();
|
||||
final topTagFilters = _topTags.where(containQuery).map((v) => TagFilter(v)).toList();
|
||||
final recentFilters = _recentTags.where(containQuery).map(TagFilter.new).toList();
|
||||
final topTagFilters = _topTags.where(containQuery).map(TagFilter.new).toList();
|
||||
return ListView(
|
||||
children: [
|
||||
Padding(
|
||||
|
|
|
@ -54,7 +54,7 @@ class FilterListDetails<T extends CollectionFilter> extends StatelessWidget {
|
|||
padding: const EdgeInsets.only(right: FilterListDetailsTheme.titleIconPadding),
|
||||
child: IconTheme(
|
||||
data: IconThemeData(color: detailsTheme.titleStyle.color),
|
||||
child: leading!,
|
||||
child: leading,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -53,7 +53,7 @@ class TagListPage extends StatelessWidget {
|
|||
}
|
||||
|
||||
List<FilterGridItem<TagFilter>> _getGridItems(CollectionSource source) {
|
||||
final filters = source.sortedTags.map((tag) => TagFilter(tag)).toSet();
|
||||
final filters = source.sortedTags.map(TagFilter.new).toSet();
|
||||
|
||||
return FilterNavigationPage.sort(settings.tagSortFactor, source, filters);
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ class CollectionSearchDelegate {
|
|||
StreamBuilder(
|
||||
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedTags.where(containQuery).map((s) => TagFilter(s));
|
||||
final filters = source.sortedTags.where(containQuery).map(TagFilter.new);
|
||||
final noFilter = TagFilter('');
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
|
@ -185,7 +185,7 @@ class CollectionSearchDelegate {
|
|||
_buildFilterRow(
|
||||
context: context,
|
||||
title: context.l10n.searchSectionRating,
|
||||
filters: [0, 5, 4, 3, 2, 1, -1].map((rating) => RatingFilter(rating)).where((f) => containQuery(f.getLabel(context))).toList(),
|
||||
filters: [0, 5, 4, 3, 2, 1, -1].map(RatingFilter.new).where((f) => containQuery(f.getLabel(context))).toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
46
lib/widgets/settings/app_export/items.dart
Normal file
46
lib/widgets/settings/app_export/items.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
import 'package:aves/model/covers.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum AppExportItem { covers, favourites, settings }
|
||||
|
||||
extension ExtraAppExportItem on AppExportItem {
|
||||
String getText(BuildContext context) {
|
||||
switch (this) {
|
||||
case AppExportItem.covers:
|
||||
return context.l10n.appExportCovers;
|
||||
case AppExportItem.favourites:
|
||||
return context.l10n.appExportFavourites;
|
||||
case AppExportItem.settings:
|
||||
return context.l10n.appExportSettings;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic export(CollectionSource source) {
|
||||
switch (this) {
|
||||
case AppExportItem.covers:
|
||||
return covers.export(source);
|
||||
case AppExportItem.favourites:
|
||||
return favourites.export(source);
|
||||
case AppExportItem.settings:
|
||||
return settings.export();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> import(dynamic jsonMap, CollectionSource source) async {
|
||||
switch (this) {
|
||||
case AppExportItem.covers:
|
||||
covers.import(jsonMap, source);
|
||||
break;
|
||||
case AppExportItem.favourites:
|
||||
favourites.import(jsonMap, source);
|
||||
break;
|
||||
case AppExportItem.settings:
|
||||
await settings.import(jsonMap);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
68
lib/widgets/settings/app_export/selection_dialog.dart
Normal file
68
lib/widgets/settings/app_export/selection_dialog.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:aves/widgets/settings/app_export/items.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppExportItemSelectionDialog extends StatefulWidget {
|
||||
final String title;
|
||||
final Set<AppExportItem>? selectableItems, initialSelection;
|
||||
|
||||
const AppExportItemSelectionDialog({
|
||||
Key? key,
|
||||
required this.title,
|
||||
this.selectableItems,
|
||||
this.initialSelection,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AppExportItemSelectionDialogState createState() => _AppExportItemSelectionDialogState();
|
||||
}
|
||||
|
||||
class _AppExportItemSelectionDialogState extends State<AppExportItemSelectionDialog> {
|
||||
final Set<AppExportItem> _selectableItems = {}, _selectedItems = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectableItems.addAll(widget.selectableItems ?? AppExportItem.values);
|
||||
_selectedItems.addAll(widget.initialSelection ?? _selectableItems);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AvesDialog(
|
||||
title: widget.title,
|
||||
scrollableContent: AppExportItem.values.map((v) {
|
||||
return SwitchListTile(
|
||||
value: _selectedItems.contains(v),
|
||||
onChanged: _selectableItems.contains(v)
|
||||
? (selected) {
|
||||
if (selected == true) {
|
||||
_selectedItems.add(v);
|
||||
} else {
|
||||
_selectedItems.remove(v);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
: null,
|
||||
title: Text(
|
||||
v.getText(context),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _selectedItems.isEmpty ? null : () => Navigator.pop(context, _selectedItems),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class CrumbLine extends StatefulWidget {
|
||||
final VolumeRelativeDirectory directory;
|
||||
|
@ -42,7 +42,7 @@ class _CrumbLineState extends State<CrumbLine> {
|
|||
Widget build(BuildContext context) {
|
||||
List<String> parts = [
|
||||
directory.getVolumeDescription(context),
|
||||
...p.split(directory.relativeDir),
|
||||
...pContext.split(directory.relativeDir),
|
||||
];
|
||||
final crumbStyle = Theme.of(context).textTheme.bodyText2;
|
||||
final crumbColor = crumbStyle!.color!.withOpacity(.4);
|
||||
|
@ -76,7 +76,7 @@ class _CrumbLineState extends State<CrumbLine> {
|
|||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final path = p.joinAll([
|
||||
final path = pContext.joinAll([
|
||||
directory.volumePath,
|
||||
...parts.skip(1).take(index),
|
||||
]);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
|
@ -14,7 +15,6 @@ import 'package:collection/collection.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class FilePicker extends StatefulWidget {
|
||||
static const routeName = '/file_picker';
|
||||
|
@ -31,7 +31,7 @@ class _FilePickerState extends State<FilePicker> {
|
|||
|
||||
Set<StorageVolume> get volumes => androidFileUtils.storageVolumes;
|
||||
|
||||
String get currentDirectoryPath => p.join(_directory.volumePath, _directory.relativeDir);
|
||||
String get currentDirectoryPath => pContext.join(_directory.volumePath, _directory.relativeDir);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -48,7 +48,7 @@ class _FilePickerState extends State<FilePicker> {
|
|||
if (showHidden) {
|
||||
return true;
|
||||
} else {
|
||||
final isHidden = p.split(v.path).last.startsWith('.');
|
||||
final isHidden = pContext.split(v.path).last.startsWith('.');
|
||||
return !isHidden;
|
||||
}
|
||||
}).toList();
|
||||
|
@ -57,7 +57,7 @@ class _FilePickerState extends State<FilePicker> {
|
|||
if (_directory.relativeDir.isEmpty) {
|
||||
return SynchronousFuture(true);
|
||||
}
|
||||
final parent = p.dirname(currentDirectoryPath);
|
||||
final parent = pContext.dirname(currentDirectoryPath);
|
||||
_goTo(parent);
|
||||
setState(() {});
|
||||
return SynchronousFuture(false);
|
||||
|
@ -143,7 +143,7 @@ class _FilePickerState extends State<FilePicker> {
|
|||
if (_directory.relativeDir.isEmpty) {
|
||||
return _directory.getVolumeDescription(context);
|
||||
}
|
||||
return p.split(_directory.relativeDir).last;
|
||||
return pContext.split(_directory.relativeDir).last;
|
||||
}
|
||||
|
||||
Widget _buildDrawer(BuildContext context) {
|
||||
|
@ -179,7 +179,7 @@ class _FilePickerState extends State<FilePicker> {
|
|||
Widget _buildContentLine(BuildContext context, FileSystemEntity content) {
|
||||
return ListTile(
|
||||
leading: const Icon(AIcons.folder),
|
||||
title: Text(p.split(content.path).last),
|
||||
title: Text(pContext.split(content.path).last),
|
||||
onTap: () {
|
||||
_goTo(content.path);
|
||||
setState(() {});
|
||||
|
@ -197,7 +197,7 @@ class _FilePickerState extends State<FilePicker> {
|
|||
contents.add(entity);
|
||||
}
|
||||
}, onDone: () {
|
||||
_contents = contents..sort((a, b) => compareAsciiUpperCase(p.split(a.path).last, p.split(b.path).last));
|
||||
_contents = contents..sort((a, b) => compareAsciiUpperCase(pContext.split(a.path).last, pContext.split(b.path).last));
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:convert';
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:aves/model/actions/settings_actions.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
|
@ -12,12 +12,15 @@ import 'package:aves/widgets/common/basic/menu.dart';
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/settings/accessibility/accessibility.dart';
|
||||
import 'package:aves/widgets/settings/app_export/items.dart';
|
||||
import 'package:aves/widgets/settings/app_export/selection_dialog.dart';
|
||||
import 'package:aves/widgets/settings/language/language.dart';
|
||||
import 'package:aves/widgets/settings/navigation/navigation.dart';
|
||||
import 'package:aves/widgets/settings/privacy/privacy.dart';
|
||||
import 'package:aves/widgets/settings/thumbnails/thumbnails.dart';
|
||||
import 'package:aves/widgets/settings/video/video.dart';
|
||||
import 'package:aves/widgets/settings/viewer/viewer.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
|
@ -106,13 +109,32 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
|
|||
);
|
||||
}
|
||||
|
||||
static const String exportVersionKey = 'version';
|
||||
static const int exportVersion = 1;
|
||||
|
||||
void _onActionSelected(SettingsAction action) async {
|
||||
final source = context.read<CollectionSource>();
|
||||
switch (action) {
|
||||
case SettingsAction.export:
|
||||
final toExport = await showDialog<Set<AppExportItem>>(
|
||||
context: context,
|
||||
builder: (context) => AppExportItemSelectionDialog(
|
||||
title: context.l10n.settingsActionExport,
|
||||
),
|
||||
);
|
||||
if (toExport == null || toExport.isEmpty) return;
|
||||
|
||||
final allMap = Map.fromEntries(toExport.map((v) {
|
||||
final jsonMap = v.export(source);
|
||||
return jsonMap != null ? MapEntry(v.name, jsonMap) : null;
|
||||
}).whereNotNull());
|
||||
allMap[exportVersionKey] = exportVersion;
|
||||
final allJsonString = jsonEncode(allMap);
|
||||
|
||||
final success = await storageService.createFile(
|
||||
'aves-settings-${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}.json',
|
||||
MimeTypes.json,
|
||||
Uint8List.fromList(utf8.encode(settings.toJson())),
|
||||
Uint8List.fromList(utf8.encode(allJsonString)),
|
||||
);
|
||||
if (success != null) {
|
||||
if (success) {
|
||||
|
@ -128,10 +150,44 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
|
|||
final bytes = await storageService.openFile();
|
||||
if (bytes.isNotEmpty) {
|
||||
try {
|
||||
await settings.fromJson(utf8.decode(bytes));
|
||||
final allJsonString = utf8.decode(bytes);
|
||||
final allJsonMap = jsonDecode(allJsonString);
|
||||
|
||||
final version = allJsonMap[exportVersionKey];
|
||||
final importable = <AppExportItem, dynamic>{};
|
||||
if (version == null) {
|
||||
// backwards compatibility before versioning
|
||||
importable[AppExportItem.settings] = allJsonMap;
|
||||
} else {
|
||||
if (allJsonMap is! Map) {
|
||||
debugPrint('failed to import app json=$allJsonMap');
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
return;
|
||||
}
|
||||
allJsonMap.keys.where((v) => v != exportVersionKey).forEach((k) {
|
||||
try {
|
||||
importable[AppExportItem.values.byName(k)] = allJsonMap[k];
|
||||
} catch (error, stack) {
|
||||
debugPrint('failed to identify import app item=$k with error=$error\n$stack');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final toImport = await showDialog<Set<AppExportItem>>(
|
||||
context: context,
|
||||
builder: (context) => AppExportItemSelectionDialog(
|
||||
title: context.l10n.settingsActionImport,
|
||||
selectableItems: importable.keys.toSet(),
|
||||
),
|
||||
);
|
||||
if (toImport == null || toImport.isEmpty) return;
|
||||
|
||||
await Future.forEach<AppExportItem>(toImport, (item) async {
|
||||
return item.import(importable[item], source);
|
||||
});
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
} catch (error) {
|
||||
debugPrint('failed to import settings, error=$error');
|
||||
debugPrint('failed to import app json, error=$error');
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,8 +133,8 @@ class StatsPage extends StatelessWidget {
|
|||
locationIndicator,
|
||||
..._buildFilterSection<String>(context, context.l10n.statsTopCountries, entryCountPerCountry, (v) => LocationFilter(LocationLevel.country, v)),
|
||||
..._buildFilterSection<String>(context, context.l10n.statsTopPlaces, entryCountPerPlace, (v) => LocationFilter(LocationLevel.place, v)),
|
||||
..._buildFilterSection<String>(context, context.l10n.statsTopTags, entryCountPerTag, (v) => TagFilter(v)),
|
||||
if (showRatings) ..._buildFilterSection<int>(context, context.l10n.searchSectionRating, entryCountPerRating, (v) => RatingFilter(v), sortByCount: false, maxRowCount: null),
|
||||
..._buildFilterSection<String>(context, context.l10n.statsTopTags, entryCountPerTag, TagFilter.new),
|
||||
if (showRatings) ..._buildFilterSection<int>(context, context.l10n.searchSectionRating, entryCountPerRating, RatingFilter.new, sortByCount: false, maxRowCount: null),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ class BasicSection extends StatelessWidget {
|
|||
if (entry.isVideo && !entry.is360) MimeFilter.video,
|
||||
if (album != null) AlbumFilter(album, collection?.source.getAlbumDisplayName(context, album)),
|
||||
if (entry.rating != 0) RatingFilter(entry.rating),
|
||||
...tags.map((tag) => TagFilter(tag)),
|
||||
...tags.map(TagFilter.new),
|
||||
};
|
||||
return AnimatedBuilder(
|
||||
animation: favourites,
|
||||
|
|
|
@ -162,6 +162,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
|
||||
// `enable-accurate-seek`: enable accurate seek
|
||||
// default: 0, in [0, 1]
|
||||
// ignore: dead_code
|
||||
options.setPlayerOption('enable-accurate-seek', accurateSeekEnabled ? 1 : 0);
|
||||
|
||||
// `min-frames`: minimal frames to stop pre-reading
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 1.5.10+64
|
|||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.14.0 <3.0.0'
|
||||
sdk: '>=2.15.0 <3.0.0'
|
||||
|
||||
# use `scripts/apply_flavor_{flavor}.sh` to set the right dependencies for the flavor
|
||||
dependencies:
|
||||
|
|
|
@ -52,17 +52,17 @@ void main() {
|
|||
setUp(() async {
|
||||
// specify Posix style path context for consistent behaviour when running tests on Windows
|
||||
getIt.registerLazySingleton<p.Context>(() => p.Context(style: p.Style.posix));
|
||||
getIt.registerLazySingleton<AvesAvailability>(() => FakeAvesAvailability());
|
||||
getIt.registerLazySingleton<MetadataDb>(() => FakeMetadataDb());
|
||||
getIt.registerLazySingleton<AvesAvailability>(FakeAvesAvailability.new);
|
||||
getIt.registerLazySingleton<MetadataDb>(FakeMetadataDb.new);
|
||||
|
||||
getIt.registerLazySingleton<AndroidAppService>(() => FakeAndroidAppService());
|
||||
getIt.registerLazySingleton<DeviceService>(() => FakeDeviceService());
|
||||
getIt.registerLazySingleton<MediaFileService>(() => FakeMediaFileService());
|
||||
getIt.registerLazySingleton<MediaStoreService>(() => FakeMediaStoreService());
|
||||
getIt.registerLazySingleton<MetadataFetchService>(() => FakeMetadataFetchService());
|
||||
getIt.registerLazySingleton<ReportService>(() => FakeReportService());
|
||||
getIt.registerLazySingleton<StorageService>(() => FakeStorageService());
|
||||
getIt.registerLazySingleton<WindowService>(() => FakeWindowService());
|
||||
getIt.registerLazySingleton<AndroidAppService>(FakeAndroidAppService.new);
|
||||
getIt.registerLazySingleton<DeviceService>(FakeDeviceService.new);
|
||||
getIt.registerLazySingleton<MediaFileService>(FakeMediaFileService.new);
|
||||
getIt.registerLazySingleton<MediaStoreService>(FakeMediaStoreService.new);
|
||||
getIt.registerLazySingleton<MetadataFetchService>(FakeMetadataFetchService.new);
|
||||
getIt.registerLazySingleton<ReportService>(FakeReportService.new);
|
||||
getIt.registerLazySingleton<StorageService>(FakeStorageService.new);
|
||||
getIt.registerLazySingleton<WindowService>(FakeWindowService.new);
|
||||
|
||||
await settings.init(monitorPlatformSettings: false);
|
||||
settings.canUseAnalysisService = false;
|
||||
|
|
|
@ -11,7 +11,7 @@ void main() {
|
|||
// specify Posix style path context for consistent behaviour when running tests on Windows
|
||||
getIt.registerLazySingleton<p.Context>(() => p.Context(style: p.Style.posix));
|
||||
|
||||
getIt.registerLazySingleton<StorageService>(() => FakeStorageService());
|
||||
getIt.registerLazySingleton<StorageService>(FakeStorageService.new);
|
||||
|
||||
await androidFileUtils.init();
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"ru": [
|
||||
"appExportCovers",
|
||||
"settingsThumbnailShowFavouriteIcon"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue