From bf88e2d8163f9c5ec4b080e5ab1caf404d21dbdb Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 29 Aug 2022 09:39:52 +0200 Subject: [PATCH 1/6] l10n --- lib/l10n/app_pt.arb | 6 ++++++ untranslated.json | 8 -------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 9ce02959a..e8f129ec7 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -87,6 +87,7 @@ "entryInfoActionEditDate": "Editar data e hora", "entryInfoActionEditLocation": "Editar localização", + "entryInfoActionEditDescription": "Editar descrição", "entryInfoActionEditRating": "Editar classificação", "entryInfoActionEditTags": "Editar etiquetas", "entryInfoActionRemoveMetadata": "Remover metadados", @@ -96,6 +97,7 @@ "filterLocationEmptyLabel": "Não localizado", "filterTagEmptyLabel": "Sem etiqueta", "filterOnThisDayLabel": "Neste dia", + "filterRecentlyAddedLabel": "Adicionado recentemente", "filterRatingUnratedLabel": "Sem classificação", "filterRatingRejectedLabel": "Rejeitado", "filterTypeAnimatedLabel": "Animado", @@ -257,6 +259,8 @@ "locationPickerUseThisLocationButton": "Usar essa localização", + "editEntryDescriptionDialogTitle": "Descrição", + "editEntryRatingDialogTitle": "Avaliação", "removeEntryMetadataDialogTitle": "Remoção de metadados", @@ -451,6 +455,7 @@ "settingsConfirmationDialogDeleteItems": "Pergunte antes de excluir itens para sempre", "settingsConfirmationDialogMoveToBinItems": "Pergunte antes de mover itens para a lixeira", "settingsConfirmationDialogMoveUndatedItems": "Pergunte antes de mover itens sem data de metadados", + "settingsConfirmationAfterMoveToBinItems": "Mostrar mensagem depois de mover itens para a lixeira", "settingsNavigationDrawerTile": "Menu de navegação", "settingsNavigationDrawerEditorTitle": "Menu de navegação", @@ -479,6 +484,7 @@ "settingsCollectionSelectionQuickActionEditorBanner": "Toque e segure para mover os botões e selecionar quais ações são exibidas ao selecionar itens.", "settingsSectionViewer": "Visualizador", + "settingsViewerGestureSideTapNext": "Toque nas bordas da tela para mostrar anterior/seguinte", "settingsViewerUseCutout": "Usar área de recorte", "settingsViewerMaximumBrightness": "Brilho máximo", "settingsMotionPhotoAutoPlay": "Reprodução automática de fotos em movimento", diff --git a/untranslated.json b/untranslated.json index bbe31db7f..b1efafdb6 100644 --- a/untranslated.json +++ b/untranslated.json @@ -22,14 +22,6 @@ "settingsViewerGestureSideTapNext" ], - "pt": [ - "entryInfoActionEditDescription", - "filterRecentlyAddedLabel", - "editEntryDescriptionDialogTitle", - "settingsConfirmationAfterMoveToBinItems", - "settingsViewerGestureSideTapNext" - ], - "ru": [ "entryInfoActionEditDescription", "filterOnThisDayLabel", From 36d92aaf38a7a18962aa5490e8c0c7a760e352d3 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 29 Aug 2022 10:04:20 +0200 Subject: [PATCH 2/6] screensaver: fixed autopilot when device orientation changes --- CHANGELOG.md | 4 ++++ lib/widgets/viewer/screen_saver_page.dart | 29 +++++++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f2a4152..091c490f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Fixed + +- screensaver stopping when device orientation changes + ## [v1.6.12] - 2022-08-27 ### Added diff --git a/lib/widgets/viewer/screen_saver_page.dart b/lib/widgets/viewer/screen_saver_page.dart index 9afc79eac..4351455bc 100644 --- a/lib/widgets/viewer/screen_saver_page.dart +++ b/lib/widgets/viewer/screen_saver_page.dart @@ -29,7 +29,7 @@ class ScreenSaverPage extends StatefulWidget { State createState() => _ScreenSaverPageState(); } -class _ScreenSaverPageState extends State { +class _ScreenSaverPageState extends State with WidgetsBindingObserver { late final ViewerController _viewerController; CollectionLens? _slideshowCollection; @@ -47,24 +47,24 @@ class _ScreenSaverPageState extends State { ); source.stateNotifier.addListener(_onSourceStateChanged); _initSlideshowCollection(); - } - - void _onSourceStateChanged() { - if (_slideshowCollection == null) { - _initSlideshowCollection(); - if (_slideshowCollection != null) { - setState(() {}); - } - } + WidgetsBinding.instance.addObserver(this); } @override void dispose() { source.stateNotifier.removeListener(_onSourceStateChanged); _viewerController.dispose(); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _viewerController.autopilot = true; + } + } + @override Widget build(BuildContext context) { Widget child; @@ -102,6 +102,15 @@ class _ScreenSaverPageState extends State { ); } + void _onSourceStateChanged() { + if (_slideshowCollection == null) { + _initSlideshowCollection(); + if (_slideshowCollection != null) { + setState(() {}); + } + } + } + void _initSlideshowCollection() { if (source.stateNotifier.value != SourceState.ready || _slideshowCollection != null) return; From 1a92768c5c23cbee75d4980692019697c0f8c691 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 29 Aug 2022 14:42:33 +0200 Subject: [PATCH 3/6] use natural order when sorting by name items, albums, tags --- CHANGELOG.md | 4 ++++ lib/model/entry.dart | 2 +- lib/model/filters/filters.dart | 2 +- lib/model/source/album.dart | 4 ++-- lib/model/source/tag.dart | 2 +- lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart | 2 +- lib/widgets/dialogs/filter_editors/create_album_dialog.dart | 2 +- lib/widgets/settings/privacy/file_picker/file_picker.dart | 2 +- lib/widgets/viewer/info/basic_section.dart | 2 +- 9 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 091c490f7..c7aeb187d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- use natural order when sorting by name items, albums, tags + ### Fixed - screensaver stopping when device orientation changes diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 606ac3a09..1a70bf464 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -819,7 +819,7 @@ class AvesEntry { // 1) title ascending // 2) extension ascending static int compareByName(AvesEntry a, AvesEntry b) { - final c = compareAsciiUpperCase(a.bestTitle ?? '', b.bestTitle ?? ''); + final c = compareAsciiUpperCaseNatural(a.bestTitle ?? '', b.bestTitle ?? ''); return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? ''); } diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 4fdd2822a..1c0b7a300 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -118,7 +118,7 @@ abstract class CollectionFilter extends Equatable implements Comparable entry.tags).toSet().toList()..sort(compareAsciiUpperCase); + final updatedTags = visibleEntries.expand((entry) => entry.tags).toSet().toList()..sort(compareAsciiUpperCaseNatural); if (!listEquals(updatedTags, sortedTags)) { sortedTags = List.unmodifiable(updatedTags); invalidateTagFilterSummary(); diff --git a/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart index 7c39adc58..545daab89 100644 --- a/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart @@ -192,7 +192,7 @@ class _TagEditorPageState extends State { return entryCountByTag.entries.toList() ..sort((kv1, kv2) { final c = kv2.value.compareTo(kv1.value); - return c != 0 ? c : compareAsciiUpperCase(kv1.key, kv2.key); + return c != 0 ? c : compareAsciiUpperCaseNatural(kv1.key, kv2.key); }); } diff --git a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart index 89312f415..c06f4a3eb 100644 --- a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart @@ -48,7 +48,7 @@ class _CreateAlbumDialogState extends State { final volumeTiles = []; if (_allVolumes.length > 1) { final byPrimary = groupBy(_allVolumes, (volume) => volume.isPrimary); - int compare(StorageVolume a, StorageVolume b) => compareAsciiUpperCase(a.path, b.path); + int compare(StorageVolume a, StorageVolume b) => compareAsciiUpperCaseNatural(a.path, b.path); final primaryVolumes = (byPrimary[true] ?? [])..sort(compare); final otherVolumes = (byPrimary[false] ?? [])..sort(compare); volumeTiles.addAll([ diff --git a/lib/widgets/settings/privacy/file_picker/file_picker.dart b/lib/widgets/settings/privacy/file_picker/file_picker.dart index 349ca7ab5..378a167f3 100644 --- a/lib/widgets/settings/privacy/file_picker/file_picker.dart +++ b/lib/widgets/settings/privacy/file_picker/file_picker.dart @@ -200,7 +200,7 @@ class _FilePickerState extends State { contents.add(entity); } }, onDone: () { - _contents = contents..sort((a, b) => compareAsciiUpperCase(pContext.split(a.path).last, pContext.split(b.path).last)); + _contents = contents..sort((a, b) => compareAsciiUpperCaseNatural(pContext.split(a.path).last, pContext.split(b.path).last)); setState(() {}); }); } diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index 2c949209a..5e599b3a6 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -58,7 +58,7 @@ class BasicSection extends StatelessWidget { } Widget _buildChips(BuildContext context) { - final tags = entry.tags.toList()..sort(compareAsciiUpperCase); + final tags = entry.tags.toList()..sort(compareAsciiUpperCaseNatural); final album = entry.directory; final filters = { MimeFilter(entry.mimeType), From b012fc9ff5e3308b87b29885ade9557b1fd4e02b Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 29 Aug 2022 20:14:13 +0200 Subject: [PATCH 4/6] #293 fixed entry duplication when media store triggered uri refresh during initial loading between entry fetch and addition --- lib/model/source/collection_source.dart | 8 +++++- lib/model/source/location.dart | 4 +-- lib/model/source/media_store_source.dart | 8 +++--- lib/model/source/tag.dart | 2 +- lib/services/analysis_service.dart | 4 +-- lib/widget_common.dart | 3 +- .../common/app_bar/app_bar_subtitle.dart | 2 +- lib/widgets/home_page.dart | 2 +- lib/widgets/viewer/screen_saver_page.dart | 3 +- test/fake/android_app_service.dart | 2 +- test/fake/availability.dart | 2 +- test/fake/device_service.dart | 2 +- test/fake/media_fetch_service.dart | 15 ++++++++++ test/fake/media_store_service.dart | 23 +++++++++------ test/fake/metadata_db.dart | 2 +- test/fake/metadata_fetch_service.dart | 2 +- test/fake/report_service.dart | 1 - test/fake/storage_service.dart | 2 +- test/fake/window_service.dart | 2 +- test/model/collection_source_test.dart | 28 +++++++++++++++++-- 20 files changed, 83 insertions(+), 34 deletions(-) create mode 100644 test/fake/media_fetch_service.dart diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index af84abb28..a9fd2886e 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -40,6 +40,12 @@ mixin SourceBase { ValueNotifier stateNotifier = ValueNotifier(SourceState.ready); + set state(SourceState value) => stateNotifier.value = value; + + SourceState get state => stateNotifier.value; + + bool get isReady => state == SourceState.ready; + ValueNotifier progressNotifier = ValueNotifier(const ProgressEvent(done: 0, total: 0)); void setProgress({required int done, required int total}) => progressNotifier.value = ProgressEvent(done: done, total: total); @@ -430,7 +436,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM updateDerivedFilters(todoEntries); } } - stateNotifier.value = SourceState.ready; + state = SourceState.ready; } // monitoring diff --git a/lib/model/source/location.dart b/lib/model/source/location.dart index 144cb683b..1b29dc43c 100644 --- a/lib/model/source/location.dart +++ b/lib/model/source/location.dart @@ -51,7 +51,7 @@ mixin LocationMixin on SourceBase { final todo = (force ? candidateEntries.where((entry) => entry.hasGps) : candidateEntries.where(locateCountriesTest)).toSet(); if (todo.isEmpty) return; - stateNotifier.value = SourceState.locatingCountries; + state = SourceState.locatingCountries; var progressDone = 0; final progressTotal = todo.length; setProgress(done: progressDone, total: progressTotal); @@ -106,7 +106,7 @@ mixin LocationMixin on SourceBase { knownLocations.putIfAbsent(approximateLatLng(entry), () => entry.addressDetails); }); - stateNotifier.value = SourceState.locatingPlaces; + state = SourceState.locatingPlaces; var progressDone = 0; final progressTotal = todo.length; setProgress(done: progressDone, total: progressTotal); diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart index 481804577..80318c595 100644 --- a/lib/model/source/media_store_source.dart +++ b/lib/model/source/media_store_source.dart @@ -42,7 +42,7 @@ class MediaStoreSource extends CollectionSource { Future _loadEssentials() async { final stopwatch = Stopwatch()..start(); - stateNotifier.value = SourceState.loading; + state = SourceState.loading; await metadataDb.init(); await favourites.init(); await covers.init(); @@ -69,7 +69,7 @@ class MediaStoreSource extends CollectionSource { }) async { debugPrint('$runtimeType refresh start'); final stopwatch = Stopwatch()..start(); - stateNotifier.value = SourceState.loading; + state = SourceState.loading; clearEntries(); final Set topEntries = {}; @@ -195,7 +195,7 @@ class MediaStoreSource extends CollectionSource { if (canAnalyze) { await analyze(analysisController, entries: analysisEntries); } else { - stateNotifier.value = SourceState.ready; + state = SourceState.ready; } // the home page may not reflect the current derived filters @@ -216,7 +216,7 @@ class MediaStoreSource extends CollectionSource { // sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg` @override Future> refreshUris(Set changedUris, {AnalysisController? analysisController}) async { - if (_initState == SourceInitializationState.none || !isMonitoring) return changedUris; + if (_initState == SourceInitializationState.none || !isMonitoring || !isReady) return changedUris; debugPrint('$runtimeType refreshUris ${changedUris.length} uris'); final uriByContentId = Map.fromEntries(changedUris.map((uri) { diff --git a/lib/model/source/tag.dart b/lib/model/source/tag.dart index 270b1ca05..f7b597a18 100644 --- a/lib/model/source/tag.dart +++ b/lib/model/source/tag.dart @@ -31,7 +31,7 @@ mixin TagMixin on SourceBase { final todo = force ? candidateEntries : candidateEntries.where(catalogEntriesTest).toSet(); if (todo.isEmpty) return; - stateNotifier.value = SourceState.cataloguing; + state = SourceState.cataloguing; var progressDone = 0; final progressTotal = todo.length; setProgress(done: progressDone, total: progressTotal); diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart index 10f916ad9..1fb764e07 100644 --- a/lib/services/analysis_service.dart +++ b/lib/services/analysis_service.dart @@ -85,7 +85,7 @@ class Analyzer { bool get isRunning => serviceState == AnalyzerState.running; - SourceState get sourceState => _source.stateNotifier.value; + SourceState get sourceState => _source.state; static const notificationUpdateInterval = Duration(seconds: 1); @@ -151,7 +151,7 @@ class Analyzer { } void _onSourceStateChanged() { - if (sourceState == SourceState.ready) { + if (_source.isReady) { _refreshApp(); _serviceStateNotifier.value = AnalyzerState.stopping; } diff --git a/lib/widget_common.dart b/lib/widget_common.dart index 79ad994ef..8a267587d 100644 --- a/lib/widget_common.dart +++ b/lib/widget_common.dart @@ -4,7 +4,6 @@ import 'package:aves/app_flavor.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/model/source/enums.dart'; import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/home_widget.dart'; @@ -64,7 +63,7 @@ Future _getWidgetEntry(int widgetId, bool reuseEntry) async { final source = MediaStoreSource(); final readyCompleter = Completer(); source.stateNotifier.addListener(() { - if (source.stateNotifier.value == SourceState.ready) { + if (source.isReady) { readyCompleter.complete(); } }); diff --git a/lib/widgets/common/app_bar/app_bar_subtitle.dart b/lib/widgets/common/app_bar/app_bar_subtitle.dart index 2a1df005d..688ca0ab5 100644 --- a/lib/widgets/common/app_bar/app_bar_subtitle.dart +++ b/lib/widgets/common/app_bar/app_bar_subtitle.dart @@ -60,7 +60,7 @@ class SourceStateSubtitle extends StatelessWidget { @override Widget build(BuildContext context) { - final sourceState = source.stateNotifier.value; + final sourceState = source.state; final subtitle = sourceState.getName(context.l10n); if (subtitle == null) return const SizedBox(); diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 3d3da1c88..e26607508 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -266,7 +266,7 @@ class _HomePageState extends State { // wait for collection to pass the `loading` state final completer = Completer(); void _onSourceStateChanged() { - if (source.stateNotifier.value != SourceState.loading) { + if (source.state != SourceState.loading) { source.stateNotifier.removeListener(_onSourceStateChanged); completer.complete(); } diff --git a/lib/widgets/viewer/screen_saver_page.dart b/lib/widgets/viewer/screen_saver_page.dart index 4351455bc..036f9f179 100644 --- a/lib/widgets/viewer/screen_saver_page.dart +++ b/lib/widgets/viewer/screen_saver_page.dart @@ -4,7 +4,6 @@ import 'package:aves/model/settings/enums/slideshow_interval.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/empty.dart'; @@ -112,7 +111,7 @@ class _ScreenSaverPageState extends State with WidgetsBindingOb } void _initSlideshowCollection() { - if (source.stateNotifier.value != SourceState.ready || _slideshowCollection != null) return; + if (!source.isReady || _slideshowCollection != null) return; final originalCollection = CollectionLens( source: source, diff --git a/test/fake/android_app_service.dart b/test/fake/android_app_service.dart index 9ec20dcd7..436d767e9 100644 --- a/test/fake/android_app_service.dart +++ b/test/fake/android_app_service.dart @@ -1,7 +1,7 @@ import 'package:aves/services/android_app_service.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/fake.dart'; class FakeAndroidAppService extends Fake implements AndroidAppService { @override diff --git a/test/fake/availability.dart b/test/fake/availability.dart index cf09187e4..b92e630c3 100644 --- a/test/fake/availability.dart +++ b/test/fake/availability.dart @@ -1,6 +1,6 @@ import 'package:aves/model/availability.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/fake.dart'; class FakeAvesAvailability extends Fake implements AvesAvailability { @override diff --git a/test/fake/device_service.dart b/test/fake/device_service.dart index a2efd54c1..c8afba4ec 100644 --- a/test/fake/device_service.dart +++ b/test/fake/device_service.dart @@ -1,6 +1,6 @@ import 'package:aves/services/device_service.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/fake.dart'; class FakeDeviceService extends Fake implements DeviceService { @override diff --git a/test/fake/media_fetch_service.dart b/test/fake/media_fetch_service.dart new file mode 100644 index 000000000..ac561a5d6 --- /dev/null +++ b/test/fake/media_fetch_service.dart @@ -0,0 +1,15 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/services/media/media_fetch_service.dart'; +import 'package:collection/collection.dart'; +import 'package:test/fake.dart'; + +class FakeMediaFetchService extends Fake implements MediaFetchService { + Duration latency = Duration.zero; + Set entries = {}; + + @override + Future getEntry(String uri, String? mimeType) async { + await Future.delayed(latency); + return entries.firstWhereOrNull((v) => v.uri == uri); + } +} diff --git a/test/fake/media_store_service.dart b/test/fake/media_store_service.dart index 1ef2e2bb9..a1097ee12 100644 --- a/test/fake/media_store_service.dart +++ b/test/fake/media_store_service.dart @@ -2,17 +2,23 @@ import 'package:aves/model/entry.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/media/media_store_service.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/fake.dart'; class FakeMediaStoreService extends Fake implements MediaStoreService { + Duration latency = Duration.zero; Set entries = {}; @override - Future> checkObsoleteContentIds(List knownContentIds) => SynchronousFuture([]); + Future> checkObsoleteContentIds(List knownContentIds) async { + await Future.delayed(latency); + return []; + } @override - Future> checkObsoletePaths(Map knownPathById) => SynchronousFuture([]); + Future> checkObsoletePaths(Map knownPathById) async { + await Future.delayed(latency); + return []; + } @override Stream getEntries(Map knownEntries, {String? directory}) => Stream.fromIterable(entries); @@ -23,14 +29,15 @@ class FakeMediaStoreService extends Fake implements MediaStoreService { static int get dateSecs => DateTime.now().millisecondsSinceEpoch ~/ 1000; - static AvesEntry newImage(String album, String filenameWithoutExtension) { - final id = nextId; + static AvesEntry newImage(String album, String filenameWithoutExtension, {int? id, int? contentId}) { + id ??= nextId; + contentId ??= id; final date = dateSecs; return AvesEntry( id: id, - uri: 'content://media/external/images/media/$id', + uri: 'content://media/external/images/media/$contentId', path: '$album/$filenameWithoutExtension.jpg', - contentId: id, + contentId: contentId, pageId: null, sourceMimeType: MimeTypes.jpeg, width: 360, diff --git a/test/fake/metadata_db.dart b/test/fake/metadata_db.dart index 665b1bb7b..49e36feba 100644 --- a/test/fake/metadata_db.dart +++ b/test/fake/metadata_db.dart @@ -7,7 +7,7 @@ import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/metadata/trash.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/fake.dart'; class FakeMetadataDb extends Fake implements MetadataDb { static int _lastId = 0; diff --git a/test/fake/metadata_fetch_service.dart b/test/fake/metadata_fetch_service.dart index 556a75bf1..1891146ea 100644 --- a/test/fake/metadata_fetch_service.dart +++ b/test/fake/metadata_fetch_service.dart @@ -2,7 +2,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/services/metadata/metadata_fetch_service.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/fake.dart'; class FakeMetadataFetchService extends Fake implements MetadataFetchService { final Map _metaMap = {}; diff --git a/test/fake/report_service.dart b/test/fake/report_service.dart index c4e8d1701..bead6f09b 100644 --- a/test/fake/report_service.dart +++ b/test/fake/report_service.dart @@ -1,6 +1,5 @@ import 'package:aves_report/aves_report.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; class FakeReportService extends ReportService { @override diff --git a/test/fake/storage_service.dart b/test/fake/storage_service.dart index 754860374..49714a4b4 100644 --- a/test/fake/storage_service.dart +++ b/test/fake/storage_service.dart @@ -1,7 +1,7 @@ import 'package:aves/services/storage_service.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/fake.dart'; class FakeStorageService extends Fake implements StorageService { static const primaryRootAlbum = '/storage/emulated/0'; diff --git a/test/fake/window_service.dart b/test/fake/window_service.dart index a87e5c2dc..500d275c3 100644 --- a/test/fake/window_service.dart +++ b/test/fake/window_service.dart @@ -1,7 +1,7 @@ import 'package:aves/services/window_service.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/fake.dart'; class FakeWindowService extends Fake implements WindowService { @override diff --git a/test/model/collection_source_test.dart b/test/model/collection_source_test.dart index 029244b25..addce76c7 100644 --- a/test/model/collection_source_test.dart +++ b/test/model/collection_source_test.dart @@ -10,11 +10,12 @@ import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/device_service.dart'; +import 'package:aves/services/media/media_fetch_service.dart'; import 'package:aves/services/media/media_store_service.dart'; import 'package:aves/services/metadata/metadata_fetch_service.dart'; import 'package:aves/services/storage_service.dart'; @@ -29,6 +30,7 @@ import 'package:path/path.dart' as p; import '../fake/android_app_service.dart'; import '../fake/availability.dart'; import '../fake/device_service.dart'; +import '../fake/media_fetch_service.dart'; import '../fake/media_store_service.dart'; import '../fake/metadata_db.dart'; import '../fake/metadata_fetch_service.dart'; @@ -57,6 +59,7 @@ void main() { getIt.registerLazySingleton(FakeAndroidAppService.new); getIt.registerLazySingleton(FakeDeviceService.new); + getIt.registerLazySingleton(FakeMediaFetchService.new); getIt.registerLazySingleton(FakeMediaStoreService.new); getIt.registerLazySingleton(FakeMetadataFetchService.new); getIt.registerLazySingleton(FakeReportService.new); @@ -65,6 +68,7 @@ void main() { await settings.init(monitorPlatformSettings: false); settings.canUseAnalysisService = false; + await androidFileUtils.init(); }); tearDown(() async { @@ -75,7 +79,7 @@ void main() { final source = MediaStoreSource(); final readyCompleter = Completer(); source.stateNotifier.addListener(() { - if (source.stateNotifier.value == SourceState.ready) { + if (source.isReady) { readyCompleter.complete(); } }); @@ -84,6 +88,26 @@ void main() { return source; } + test('initial load v. refresh race condition', () async { + const latency = Duration(milliseconds: 100); + + final loadEntry = FakeMediaStoreService.newImage(testAlbum, 'image1', id: -1, contentId: 1); + final refreshEntry = FakeMediaStoreService.newImage(testAlbum, 'image1', id: -1, contentId: 1); + (mediaStoreService as FakeMediaStoreService) + ..entries = {loadEntry} + ..latency = latency; + (mediaFetchService as FakeMediaFetchService).entries = {refreshEntry}; + + final source = MediaStoreSource(); + unawaited(source.init()); + await Future.delayed(const Duration(milliseconds: 10)); + expect(source.initState, SourceInitializationState.full); + await source.refreshUris({refreshEntry.uri}); + + await Future.delayed(const Duration(seconds: 1)); + expect(source.allEntries.length, 1); + }); + test('album/country/tag hidden on launch when their items are hidden by entry prop', () async { settings.hiddenFilters = {const AlbumFilter(testAlbum, 'whatever')}; From 49b957faeadf17ca55a1e876d6b608f895975d4f Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 29 Aug 2022 21:30:18 +0200 Subject: [PATCH 5/6] fixed tests --- test/fake/media_fetch_service.dart | 2 -- test/fake/media_store_service.dart | 13 +++++++++---- test/model/collection_source_test.dart | 9 ++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/test/fake/media_fetch_service.dart b/test/fake/media_fetch_service.dart index ac561a5d6..fa9645ace 100644 --- a/test/fake/media_fetch_service.dart +++ b/test/fake/media_fetch_service.dart @@ -4,12 +4,10 @@ import 'package:collection/collection.dart'; import 'package:test/fake.dart'; class FakeMediaFetchService extends Fake implements MediaFetchService { - Duration latency = Duration.zero; Set entries = {}; @override Future getEntry(String uri, String? mimeType) async { - await Future.delayed(latency); return entries.firstWhereOrNull((v) => v.uri == uri); } } diff --git a/test/fake/media_store_service.dart b/test/fake/media_store_service.dart index a1097ee12..0649476ab 100644 --- a/test/fake/media_store_service.dart +++ b/test/fake/media_store_service.dart @@ -5,18 +5,23 @@ import 'package:aves/services/media/media_store_service.dart'; import 'package:test/fake.dart'; class FakeMediaStoreService extends Fake implements MediaStoreService { - Duration latency = Duration.zero; - Set entries = {}; + late Set entries; + Duration? latency; + + void reset() { + entries = {}; + latency = null; + } @override Future> checkObsoleteContentIds(List knownContentIds) async { - await Future.delayed(latency); + if (latency != null) await Future.delayed(latency!); return []; } @override Future> checkObsoletePaths(Map knownPathById) async { - await Future.delayed(latency); + if (latency != null) await Future.delayed(latency!); return []; } diff --git a/test/model/collection_source_test.dart b/test/model/collection_source_test.dart index addce76c7..9858c8114 100644 --- a/test/model/collection_source_test.dart +++ b/test/model/collection_source_test.dart @@ -51,7 +51,7 @@ void main() { countryName: 'AUS', ); - setUp(() async { + setUpAll(() async { // specify Posix style path context for consistent behaviour when running tests on Windows getIt.registerLazySingleton(() => p.Context(style: p.Style.posix)); getIt.registerLazySingleton(FakeAvesAvailability.new); @@ -71,7 +71,11 @@ void main() { await androidFileUtils.init(); }); - tearDown(() async { + setUp(() async { + (getIt() as FakeMediaStoreService).reset(); + }); + + tearDownAll(() async { await getIt.reset(); }); @@ -360,7 +364,6 @@ void main() { FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Pictures/Arendt', '1'), }; - await androidFileUtils.init(); final source = await _initSource(); await tester.pumpWidget( Builder( From c97c1fa0e9597b9b0115b926711649b5ebd24fa7 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 29 Aug 2022 21:35:00 +0200 Subject: [PATCH 6/6] version bump --- CHANGELOG.md | 3 +++ fastlane/metadata/android/en-US/changelogs/1079.txt | 5 +++++ pubspec.yaml | 2 +- whatsnew/whatsnew-en-US | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/1079.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index c7aeb187d..61d67a565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v1.6.13] - 2022-08-29 + ### Changed - use natural order when sorting by name items, albums, tags ### Fixed +- adding duplicate items during loading in some cases - screensaver stopping when device orientation changes ## [v1.6.12] - 2022-08-27 diff --git a/fastlane/metadata/android/en-US/changelogs/1079.txt b/fastlane/metadata/android/en-US/changelogs/1079.txt new file mode 100644 index 000000000..30000cd00 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/1079.txt @@ -0,0 +1,5 @@ +In v1.6.13: +- play your HEIC motion photos +- find recently downloaded images with the `recently added` filter +- enjoy the app in Dutch +Full changelog available on GitHub \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index cf390fad3..83374102b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/deckerst/aves # - github changelog: /CHANGELOG.md # - play changelog: /whatsnew/whatsnew-en-US # - izzy changelog: /fastlane/metadata/android/en-US/changelogs/1XXX.txt -version: 1.6.12+78 +version: 1.6.13+79 publish_to: none environment: diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index 756c775df..30000cd00 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,4 +1,4 @@ -In v1.6.12: +In v1.6.13: - play your HEIC motion photos - find recently downloaded images with the `recently added` filter - enjoy the app in Dutch