warn and optionally set metadata date before moving undated items
This commit is contained in:
parent
150e94cee5
commit
1101400ae2
14 changed files with 289 additions and 130 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## <a id="unreleased"></a>[Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Theme: light/dark/black and poly/monochrome settings
|
||||
- Video: speed and muted state indicators
|
||||
- Info: option to set date from other item
|
||||
- warn and optionally set metadata date before moving undated items
|
||||
|
||||
### Changed
|
||||
|
||||
- Viewer: quick action defaults
|
||||
|
||||
### Fixed
|
||||
|
||||
- app launch despite faulty storage volumes on Android 11+
|
||||
|
||||
## <a id="v1.6.2"></a>[v1.6.2] - 2022-03-07
|
||||
|
||||
### Added
|
||||
|
|
|
@ -286,6 +286,8 @@
|
|||
"count": {}
|
||||
}
|
||||
},
|
||||
"moveUndatedConfirmationDialogMessage": "Some items have no metadata date. Their current date will be reset by this operation unless a metadata date is set.",
|
||||
"moveUndatedConfirmationDialogSetDate": "Set date",
|
||||
|
||||
"videoResumeDialogMessage": "Do you want to resume playing at {time}?",
|
||||
"@videoResumeDialogMessage": {
|
||||
|
@ -568,6 +570,7 @@
|
|||
"settingsConfirmationDialogTitle": "Confirmation Dialogs",
|
||||
"settingsConfirmationDialogDeleteItems": "Ask before deleting items forever",
|
||||
"settingsConfirmationDialogMoveToBinItems": "Ask before moving items to the recycle bin",
|
||||
"settingsConfirmationDialogMoveUndatedItems": "Ask before moving items without a metadata date",
|
||||
|
||||
"settingsNavigationDrawerTile": "Navigation menu",
|
||||
"settingsNavigationDrawerEditorTitle": "Navigation Menu",
|
||||
|
|
|
@ -23,7 +23,10 @@ class SettingsDefaults {
|
|||
static const mustBackTwiceToExit = true;
|
||||
static const keepScreenOn = KeepScreenOn.viewerOnly;
|
||||
static const homePage = HomePageSetting.collection;
|
||||
static const confirmationDialogs = ConfirmationDialog.values;
|
||||
static const confirmDeleteForever = true;
|
||||
static const confirmMoveToBin = true;
|
||||
static const confirmMoveUndatedItems = true;
|
||||
static const setMetadataDateBeforeFileOp = false;
|
||||
static final drawerTypeBookmarks = [
|
||||
null,
|
||||
MimeFilter.video,
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraConfirmationDialog on ConfirmationDialog {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case ConfirmationDialog.delete:
|
||||
return context.l10n.settingsConfirmationDialogDeleteItems;
|
||||
case ConfirmationDialog.moveToBin:
|
||||
return context.l10n.settingsConfirmationDialogMoveToBinItems;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ enum AvesThemeColorMode { monochrome, polychrome }
|
|||
|
||||
enum AvesThemeBrightness { system, light, dark, black }
|
||||
|
||||
enum ConfirmationDialog { delete, moveToBin }
|
||||
enum ConfirmationDialog { deleteForever, moveToBin, moveUndatedItems }
|
||||
|
||||
enum CoordinateFormat { dms, decimal }
|
||||
|
||||
|
|
|
@ -52,7 +52,10 @@ class Settings extends ChangeNotifier {
|
|||
static const mustBackTwiceToExitKey = 'must_back_twice_to_exit';
|
||||
static const keepScreenOnKey = 'keep_screen_on';
|
||||
static const homePageKey = 'home_page';
|
||||
static const confirmationDialogsKey = 'confirmation_dialogs';
|
||||
static const confirmDeleteForeverKey = 'confirm_delete_forever';
|
||||
static const confirmMoveToBinKey = 'confirm_move_to_bin';
|
||||
static const confirmMoveUndatedItemsKey = 'confirm_move_undated_items';
|
||||
static const setMetadataDateBeforeFileOpKey = 'set_metadata_date_before_file_op';
|
||||
static const drawerTypeBookmarksKey = 'drawer_type_bookmarks';
|
||||
static const drawerAlbumBookmarksKey = 'drawer_album_bookmarks';
|
||||
static const drawerPageBookmarksKey = 'drawer_page_bookmarks';
|
||||
|
@ -279,9 +282,21 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString());
|
||||
|
||||
List<ConfirmationDialog> get confirmationDialogs => getEnumListOrDefault(confirmationDialogsKey, SettingsDefaults.confirmationDialogs, ConfirmationDialog.values);
|
||||
bool get confirmDeleteForever => getBoolOrDefault(confirmDeleteForeverKey, SettingsDefaults.confirmDeleteForever);
|
||||
|
||||
set confirmationDialogs(List<ConfirmationDialog> newValue) => setAndNotify(confirmationDialogsKey, newValue.map((v) => v.toString()).toList());
|
||||
set confirmDeleteForever(bool newValue) => setAndNotify(confirmDeleteForeverKey, newValue);
|
||||
|
||||
bool get confirmMoveToBin => getBoolOrDefault(confirmMoveToBinKey, SettingsDefaults.confirmMoveToBin);
|
||||
|
||||
set confirmMoveToBin(bool newValue) => setAndNotify(confirmMoveToBinKey, newValue);
|
||||
|
||||
bool get confirmMoveUndatedItems => getBoolOrDefault(confirmMoveUndatedItemsKey, SettingsDefaults.confirmMoveUndatedItems);
|
||||
|
||||
set confirmMoveUndatedItems(bool newValue) => setAndNotify(confirmMoveUndatedItemsKey, newValue);
|
||||
|
||||
bool get setMetadataDateBeforeFileOp => getBoolOrDefault(setMetadataDateBeforeFileOpKey, SettingsDefaults.setMetadataDateBeforeFileOp);
|
||||
|
||||
set setMetadataDateBeforeFileOp(bool newValue) => setAndNotify(setMetadataDateBeforeFileOpKey, newValue);
|
||||
|
||||
List<CollectionFilter?> get drawerTypeBookmarks =>
|
||||
(getStringList(drawerTypeBookmarksKey))?.map((v) {
|
||||
|
@ -656,6 +671,10 @@ class Settings extends ChangeNotifier {
|
|||
case isInstalledAppAccessAllowedKey:
|
||||
case isErrorReportingAllowedKey:
|
||||
case mustBackTwiceToExitKey:
|
||||
case confirmDeleteForeverKey:
|
||||
case confirmMoveToBinKey:
|
||||
case confirmMoveUndatedItemsKey:
|
||||
case setMetadataDateBeforeFileOpKey:
|
||||
case showThumbnailFavouriteKey:
|
||||
case showThumbnailLocationKey:
|
||||
case showThumbnailMotionPhotoKey:
|
||||
|
@ -710,7 +729,6 @@ class Settings extends ChangeNotifier {
|
|||
debugPrint('failed to import key=$key, value=$newValue is not a string');
|
||||
}
|
||||
break;
|
||||
case confirmationDialogsKey:
|
||||
case drawerTypeBookmarksKey:
|
||||
case drawerAlbumBookmarksKey:
|
||||
case drawerPageBookmarksKey:
|
||||
|
|
|
@ -75,7 +75,7 @@ mixin LocationMixin on SourceBase {
|
|||
// full reverse geocoding, requiring Play Services and some connectivity
|
||||
Future<void> _locatePlaces(AnalysisController controller, Set<AvesEntry> candidateEntries) async {
|
||||
if (controller.isStopping) return;
|
||||
if (!(await availability.canLocatePlaces)) return;
|
||||
if (!await availability.canLocatePlaces) return;
|
||||
|
||||
final force = controller.force;
|
||||
final todo = (force ? candidateEntries.where((entry) => entry.hasGps) : candidateEntries.where(locatePlacesTest)).toSet();
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/model/entry_metadata_edition.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/metadata/date_modifier.dart';
|
||||
import 'package:aves/model/query.dart';
|
||||
import 'package:aves/model/selection.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
|
@ -197,7 +198,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
_flip(context);
|
||||
break;
|
||||
case EntrySetAction.editDate:
|
||||
_editDate(context);
|
||||
editDate(context);
|
||||
break;
|
||||
case EntrySetAction.editLocation:
|
||||
_editLocation(context);
|
||||
|
@ -214,6 +215,11 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
}
|
||||
}
|
||||
|
||||
void _leaveSelectionMode(BuildContext context) {
|
||||
final selection = context.read<Selection<AvesEntry>?>();
|
||||
selection?.browse();
|
||||
}
|
||||
|
||||
Set<AvesEntry> _getTargetItems(BuildContext context) {
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
final groupedEntries = (selection.isSelecting ? selection.selectedItems : context.read<CollectionLens>().sortedEntries);
|
||||
|
@ -234,8 +240,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final collection = context.read<CollectionLens>();
|
||||
collection.source.analyze(controller, entries: entries);
|
||||
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
selection.browse();
|
||||
_leaveSelectionMode(context);
|
||||
}
|
||||
|
||||
Future<void> _toggleFavourite(BuildContext context) async {
|
||||
|
@ -246,8 +251,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
await favourites.add(entries);
|
||||
}
|
||||
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
selection.browse();
|
||||
_leaveSelectionMode(context);
|
||||
}
|
||||
|
||||
Future<void> _delete(BuildContext context) async {
|
||||
|
@ -264,12 +268,12 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final selectionDirs = entries.map((e) => e.directory).whereNotNull().toSet();
|
||||
final todoCount = entries.length;
|
||||
|
||||
if (!(await showConfirmationDialog(
|
||||
if (!await showConfirmationDialog(
|
||||
context: context,
|
||||
type: ConfirmationDialog.delete,
|
||||
type: ConfirmationDialog.deleteForever,
|
||||
message: l10n.deleteEntriesConfirmationDialogMessage(todoCount),
|
||||
confirmationButtonLabel: l10n.deleteButtonLabel,
|
||||
))) return;
|
||||
)) return;
|
||||
|
||||
if (!pureTrash && !await checkStoragePermissionForAlbums(context, selectionDirs, entries: entries)) return;
|
||||
|
||||
|
@ -298,23 +302,22 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
},
|
||||
);
|
||||
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
selection.browse();
|
||||
_leaveSelectionMode(context);
|
||||
}
|
||||
|
||||
Future<void> _move(BuildContext context, {required MoveType moveType}) async {
|
||||
final entries = _getTargetItems(context);
|
||||
await move(context, moveType: moveType, entries: entries);
|
||||
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
selection.browse();
|
||||
_leaveSelectionMode(context);
|
||||
}
|
||||
|
||||
Future<void> _edit(
|
||||
BuildContext context,
|
||||
Set<AvesEntry> todoItems,
|
||||
Future<Set<EntryDataType>> Function(AvesEntry entry) op,
|
||||
) async {
|
||||
Future<Set<EntryDataType>> Function(AvesEntry entry) op, {
|
||||
bool showResult = true,
|
||||
}) async {
|
||||
final selectionDirs = todoItems.map((e) => e.directory).whereNotNull().toSet();
|
||||
final todoCount = todoItems.length;
|
||||
|
||||
|
@ -355,6 +358,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
}
|
||||
}));
|
||||
|
||||
if (showResult) {
|
||||
final l10n = context.l10n;
|
||||
final successCount = successOps.length;
|
||||
if (successCount < todoCount) {
|
||||
|
@ -364,10 +368,10 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final count = editedOps.length;
|
||||
showFeedback(context, l10n.collectionEditSuccessFeedback(count));
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
selection.browse();
|
||||
_leaveSelectionMode(context);
|
||||
}
|
||||
|
||||
Future<Set<AvesEntry>?> _getEditableTargetItems(
|
||||
|
@ -410,78 +414,80 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
}
|
||||
|
||||
Future<void> _rotate(BuildContext context, {required bool clockwise}) async {
|
||||
final todoItems = await _getEditableTargetItems(context, canEdit: (entry) => entry.canRotateAndFlip);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canRotateAndFlip);
|
||||
if (entries == null || entries.isEmpty) return;
|
||||
|
||||
await _edit(context, todoItems, (entry) => entry.rotate(clockwise: clockwise));
|
||||
await _edit(context, entries, (entry) => entry.rotate(clockwise: clockwise));
|
||||
}
|
||||
|
||||
Future<void> _flip(BuildContext context) async {
|
||||
final todoItems = await _getEditableTargetItems(context, canEdit: (entry) => entry.canRotateAndFlip);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canRotateAndFlip);
|
||||
if (entries == null || entries.isEmpty) return;
|
||||
|
||||
await _edit(context, todoItems, (entry) => entry.flip());
|
||||
await _edit(context, entries, (entry) => entry.flip());
|
||||
}
|
||||
|
||||
Future<void> _editDate(BuildContext context) async {
|
||||
final todoItems = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditDate);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
Future<void> editDate(BuildContext context, {Set<AvesEntry>? entries, DateModifier? modifier, bool showResult = true}) async {
|
||||
entries ??= await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditDate);
|
||||
if (entries == null || entries.isEmpty) return;
|
||||
|
||||
if (modifier == null) {
|
||||
final collection = context.read<CollectionLens>();
|
||||
final modifier = await selectDateModifier(context, todoItems, collection);
|
||||
modifier = await selectDateModifier(context, entries, collection);
|
||||
}
|
||||
if (modifier == null) return;
|
||||
|
||||
await _edit(context, todoItems, (entry) => entry.editDate(modifier));
|
||||
await _edit(context, entries, (entry) => entry.editDate(modifier!), showResult: showResult);
|
||||
}
|
||||
|
||||
Future<void> _editLocation(BuildContext context) async {
|
||||
final todoItems = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditLocation);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditLocation);
|
||||
if (entries == null || entries.isEmpty) return;
|
||||
|
||||
final collection = context.read<CollectionLens>();
|
||||
final location = await selectLocation(context, todoItems, collection);
|
||||
final location = await selectLocation(context, entries, collection);
|
||||
if (location == null) return;
|
||||
|
||||
await _edit(context, todoItems, (entry) => entry.editLocation(location));
|
||||
await _edit(context, entries, (entry) => entry.editLocation(location));
|
||||
}
|
||||
|
||||
Future<void> _editRating(BuildContext context) async {
|
||||
final todoItems = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditRating);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditRating);
|
||||
if (entries == null || entries.isEmpty) return;
|
||||
|
||||
final rating = await selectRating(context, todoItems);
|
||||
final rating = await selectRating(context, entries);
|
||||
if (rating == null) return;
|
||||
|
||||
await _edit(context, todoItems, (entry) => entry.editRating(rating));
|
||||
await _edit(context, entries, (entry) => entry.editRating(rating));
|
||||
}
|
||||
|
||||
Future<void> _editTags(BuildContext context) async {
|
||||
final todoItems = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditTags);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditTags);
|
||||
if (entries == null || entries.isEmpty) return;
|
||||
|
||||
final newTagsByEntry = await selectTags(context, todoItems);
|
||||
final newTagsByEntry = await selectTags(context, entries);
|
||||
if (newTagsByEntry == null) return;
|
||||
|
||||
// only process modified items
|
||||
todoItems.removeWhere((entry) {
|
||||
entries.removeWhere((entry) {
|
||||
final newTags = newTagsByEntry[entry] ?? entry.tags;
|
||||
final currentTags = entry.tags;
|
||||
return newTags.length == currentTags.length && newTags.every(currentTags.contains);
|
||||
});
|
||||
|
||||
if (todoItems.isEmpty) return;
|
||||
if (entries.isEmpty) return;
|
||||
|
||||
await _edit(context, todoItems, (entry) => entry.editTags(newTagsByEntry[entry]!));
|
||||
await _edit(context, entries, (entry) => entry.editTags(newTagsByEntry[entry]!));
|
||||
}
|
||||
|
||||
Future<void> _removeMetadata(BuildContext context) async {
|
||||
final todoItems = await _getEditableTargetItems(context, canEdit: (entry) => entry.canRemoveMetadata);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canRemoveMetadata);
|
||||
if (entries == null || entries.isEmpty) return;
|
||||
|
||||
final types = await selectMetadataToRemove(context, todoItems);
|
||||
final types = await selectMetadataToRemove(context, entries);
|
||||
if (types == null || types.isEmpty) return;
|
||||
|
||||
await _edit(context, todoItems, (entry) => entry.removeMetadata(types));
|
||||
await _edit(context, entries, (entry) => entry.removeMetadata(types));
|
||||
}
|
||||
|
||||
void _goToMap(BuildContext context) {
|
||||
|
|
|
@ -7,7 +7,10 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/trash.dart';
|
||||
import 'package:aves/model/highlight.dart';
|
||||
import 'package:aves/model/metadata/date_modifier.dart';
|
||||
import 'package:aves/model/metadata/enums.dart';
|
||||
import 'package:aves/model/settings/enums/enums.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/services/common/image_op_events.dart';
|
||||
|
@ -16,6 +19,7 @@ import 'package:aves/services/media/enums.dart';
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/collection/entry_set_action_delegate.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||
|
@ -42,12 +46,12 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
|
||||
final l10n = context.l10n;
|
||||
if (toBin) {
|
||||
if (!(await showConfirmationDialog(
|
||||
if (!await showConfirmationDialog(
|
||||
context: context,
|
||||
type: ConfirmationDialog.moveToBin,
|
||||
message: l10n.binEntriesConfirmationDialogMessage(todoCount),
|
||||
confirmationButtonLabel: l10n.deleteButtonLabel,
|
||||
))) return;
|
||||
)) return;
|
||||
}
|
||||
|
||||
final entriesByDestination = <String, Set<AvesEntry>>{};
|
||||
|
@ -108,6 +112,8 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
}
|
||||
}
|
||||
|
||||
if ({MoveType.move, MoveType.copy}.contains(moveType) && !await _checkUndatedItems(context, entries)) return;
|
||||
|
||||
final source = context.read<CollectionSource>();
|
||||
source.pauseMonitoring();
|
||||
final opId = mediaFileService.newOpId;
|
||||
|
@ -196,4 +202,60 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _checkUndatedItems(BuildContext context, Set<AvesEntry> entries) async {
|
||||
final undatedItems = entries.where((entry) {
|
||||
if (!entry.isCatalogued) return false;
|
||||
final dateMillis = entry.catalogMetadata?.dateMillis;
|
||||
return dateMillis == null || dateMillis == 0;
|
||||
}).toSet();
|
||||
if (undatedItems.isNotEmpty) {
|
||||
if (!await showConfirmationDialog(
|
||||
context: context,
|
||||
type: ConfirmationDialog.moveUndatedItems,
|
||||
delegate: MoveUndatedConfirmationDialogDelegate(),
|
||||
confirmationButtonLabel: context.l10n.continueButtonLabel,
|
||||
)) return false;
|
||||
|
||||
if (settings.setMetadataDateBeforeFileOp) {
|
||||
final entriesToDate = undatedItems.where((entry) => entry.canEditDate).toSet();
|
||||
if (entriesToDate.isNotEmpty) {
|
||||
await EntrySetActionDelegate().editDate(
|
||||
context,
|
||||
entries: entriesToDate,
|
||||
modifier: DateModifier.copyField(DateFieldSource.fileModifiedDate),
|
||||
showResult: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class MoveUndatedConfirmationDialogDelegate extends ConfirmationDialogDelegate {
|
||||
final ValueNotifier<bool> _setMetadataDate = ValueNotifier(false);
|
||||
|
||||
MoveUndatedConfirmationDialogDelegate() {
|
||||
_setMetadataDate.value = settings.setMetadataDateBeforeFileOp;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Widget> build(BuildContext context) => [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16) + const EdgeInsets.only(top: 8),
|
||||
child: Text(context.l10n.moveUndatedConfirmationDialogMessage),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _setMetadataDate,
|
||||
builder: (context, flag, child) => SwitchListTile(
|
||||
value: flag,
|
||||
onChanged: (v) => _setMetadataDate.value = v,
|
||||
title: Text(context.l10n.moveUndatedConfirmationDialogSetDate),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
void apply() => settings.setMetadataDateBeforeFileOp = _setMetadataDate.value;
|
||||
}
|
||||
|
|
|
@ -5,56 +5,107 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'aves_dialog.dart';
|
||||
|
||||
abstract class ConfirmationDialogDelegate {
|
||||
List<Widget> build(BuildContext context);
|
||||
|
||||
void apply() {}
|
||||
}
|
||||
|
||||
class MessageConfirmationDialogDelegate extends ConfirmationDialogDelegate {
|
||||
final String message;
|
||||
|
||||
MessageConfirmationDialogDelegate(this.message);
|
||||
|
||||
@override
|
||||
List<Widget> build(BuildContext context) => [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16) + const EdgeInsets.only(top: 8),
|
||||
child: Text(message),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<bool> showConfirmationDialog({
|
||||
required BuildContext context,
|
||||
required ConfirmationDialog type,
|
||||
required String message,
|
||||
String? message,
|
||||
ConfirmationDialogDelegate? delegate,
|
||||
required String confirmationButtonLabel,
|
||||
}) async {
|
||||
if (!settings.confirmationDialogs.contains(type)) return true;
|
||||
if (!_shouldConfirm(type)) return true;
|
||||
|
||||
assert((message != null) ^ (delegate != null));
|
||||
final effectiveDelegate = delegate ?? MessageConfirmationDialogDelegate(message!);
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AvesConfirmationDialog(
|
||||
builder: (context) => _AvesConfirmationDialog(
|
||||
type: type,
|
||||
message: message,
|
||||
delegate: effectiveDelegate,
|
||||
confirmationButtonLabel: confirmationButtonLabel,
|
||||
),
|
||||
);
|
||||
return confirmed == true;
|
||||
if (confirmed == null) return false;
|
||||
|
||||
if (confirmed) {
|
||||
effectiveDelegate.apply();
|
||||
}
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
class AvesConfirmationDialog extends StatefulWidget {
|
||||
final ConfirmationDialog type;
|
||||
final String message, confirmationButtonLabel;
|
||||
bool _shouldConfirm(ConfirmationDialog type) {
|
||||
switch (type) {
|
||||
case ConfirmationDialog.deleteForever:
|
||||
return settings.confirmDeleteForever;
|
||||
case ConfirmationDialog.moveToBin:
|
||||
return settings.confirmMoveToBin;
|
||||
case ConfirmationDialog.moveUndatedItems:
|
||||
return settings.confirmMoveUndatedItems;
|
||||
}
|
||||
}
|
||||
|
||||
const AvesConfirmationDialog({
|
||||
void _skipConfirmation(ConfirmationDialog type) {
|
||||
switch (type) {
|
||||
case ConfirmationDialog.deleteForever:
|
||||
settings.confirmDeleteForever = false;
|
||||
break;
|
||||
case ConfirmationDialog.moveToBin:
|
||||
settings.confirmMoveToBin = false;
|
||||
break;
|
||||
case ConfirmationDialog.moveUndatedItems:
|
||||
settings.confirmMoveUndatedItems = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class _AvesConfirmationDialog extends StatefulWidget {
|
||||
final ConfirmationDialog type;
|
||||
final ConfirmationDialogDelegate delegate;
|
||||
final String confirmationButtonLabel;
|
||||
|
||||
const _AvesConfirmationDialog({
|
||||
Key? key,
|
||||
required this.type,
|
||||
required this.message,
|
||||
required this.delegate,
|
||||
required this.confirmationButtonLabel,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AvesConfirmationDialog> createState() => _AvesConfirmationDialogState();
|
||||
State<_AvesConfirmationDialog> createState() => _AvesConfirmationDialogState();
|
||||
}
|
||||
|
||||
class _AvesConfirmationDialogState extends State<AvesConfirmationDialog> {
|
||||
final ValueNotifier<bool> _skipConfirmation = ValueNotifier(false);
|
||||
class _AvesConfirmationDialogState extends State<_AvesConfirmationDialog> {
|
||||
final ValueNotifier<bool> _skip = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AvesDialog(
|
||||
scrollableContent: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16) + const EdgeInsets.only(top: 8),
|
||||
child: Text(widget.message),
|
||||
),
|
||||
...widget.delegate.build(context),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _skipConfirmation,
|
||||
builder: (context, ask, child) => SwitchListTile(
|
||||
value: ask,
|
||||
onChanged: (v) => _skipConfirmation.value = v,
|
||||
valueListenable: _skip,
|
||||
builder: (context, flag, child) => SwitchListTile(
|
||||
value: flag,
|
||||
onChanged: (v) => _skip.value = v,
|
||||
title: Text(context.l10n.doNotAskAgain),
|
||||
),
|
||||
),
|
||||
|
@ -66,8 +117,8 @@ class _AvesConfirmationDialogState extends State<AvesConfirmationDialog> {
|
|||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (_skipConfirmation.value) {
|
||||
settings.confirmationDialogs = settings.confirmationDialogs.toList()..remove(widget.type);
|
||||
if (_skip.value) {
|
||||
_skipConfirmation(widget.type);
|
||||
}
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
|
|
|
@ -290,7 +290,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
|
||||
if (!await checkStoragePermissionForAlbums(context, {album})) return;
|
||||
|
||||
if (!(await File(destinationAlbum).exists())) {
|
||||
if (!await File(destinationAlbum).exists()) {
|
||||
// access to the destination parent is required to create the underlying destination folder
|
||||
if (!await checkStoragePermissionForAlbums(context, {destinationAlbumParent})) return;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import 'package:aves/model/settings/enums/confirmation_dialogs.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/settings/common/tiles.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ConfirmationDialogTile extends StatelessWidget {
|
||||
const ConfirmationDialogTile({Key? key}) : super(key: key);
|
||||
|
@ -37,29 +35,23 @@ class ConfirmationDialogPage extends StatelessWidget {
|
|||
title: Text(context.l10n.settingsConfirmationDialogTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Selector<Settings, List<ConfirmationDialog>>(
|
||||
selector: (context, s) => s.confirmationDialogs,
|
||||
builder: (context, current, child) => ListView(
|
||||
children: [
|
||||
ConfirmationDialog.moveToBin,
|
||||
ConfirmationDialog.delete,
|
||||
]
|
||||
.map((dialog) => SwitchListTile(
|
||||
value: current.contains(dialog),
|
||||
onChanged: (v) {
|
||||
final dialogs = current.toList();
|
||||
if (v) {
|
||||
dialogs.add(dialog);
|
||||
} else {
|
||||
dialogs.remove(dialog);
|
||||
}
|
||||
settings.confirmationDialogs = dialogs;
|
||||
},
|
||||
title: Text(dialog.getName(context)),
|
||||
))
|
||||
.toList(),
|
||||
child: ListView(children: [
|
||||
SettingsSwitchListTile(
|
||||
selector: (context, s) => s.confirmMoveUndatedItems,
|
||||
onChanged: (v) => settings.confirmMoveUndatedItems = v,
|
||||
title: context.l10n.settingsConfirmationDialogMoveUndatedItems,
|
||||
),
|
||||
SettingsSwitchListTile(
|
||||
selector: (context, s) => s.confirmMoveToBin,
|
||||
onChanged: (v) => settings.confirmMoveToBin = v,
|
||||
title: context.l10n.settingsConfirmationDialogMoveToBinItems,
|
||||
),
|
||||
SettingsSwitchListTile(
|
||||
selector: (context, s) => s.confirmDeleteForever,
|
||||
onChanged: (v) => settings.confirmDeleteForever = v,
|
||||
title: context.l10n.settingsConfirmationDialogDeleteItems,
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -187,12 +187,12 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
}
|
||||
|
||||
final l10n = context.l10n;
|
||||
if (!(await showConfirmationDialog(
|
||||
if (!await showConfirmationDialog(
|
||||
context: context,
|
||||
type: ConfirmationDialog.delete,
|
||||
type: ConfirmationDialog.deleteForever,
|
||||
message: l10n.deleteEntriesConfirmationDialogMessage(1),
|
||||
confirmationButtonLabel: l10n.deleteButtonLabel,
|
||||
))) return;
|
||||
)) return;
|
||||
|
||||
if (!await checkStoragePermission(context, {entry})) return;
|
||||
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"moveUndatedConfirmationDialogMessage",
|
||||
"moveUndatedConfirmationDialogSetDate",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsConfirmationDialogMoveUndatedItems",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -13,7 +16,10 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"moveUndatedConfirmationDialogMessage",
|
||||
"moveUndatedConfirmationDialogSetDate",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsConfirmationDialogMoveUndatedItems",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -23,7 +29,10 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"moveUndatedConfirmationDialogMessage",
|
||||
"moveUndatedConfirmationDialogSetDate",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsConfirmationDialogMoveUndatedItems",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -39,7 +48,10 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"moveUndatedConfirmationDialogMessage",
|
||||
"moveUndatedConfirmationDialogSetDate",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsConfirmationDialogMoveUndatedItems",
|
||||
"settingsViewerShowOverlayThumbnails",
|
||||
"settingsVideoControlsTile",
|
||||
"settingsVideoControlsTitle",
|
||||
|
@ -56,7 +68,10 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"moveUndatedConfirmationDialogMessage",
|
||||
"moveUndatedConfirmationDialogSetDate",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsConfirmationDialogMoveUndatedItems",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -66,7 +81,10 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"moveUndatedConfirmationDialogMessage",
|
||||
"moveUndatedConfirmationDialogSetDate",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsConfirmationDialogMoveUndatedItems",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -76,7 +94,10 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"moveUndatedConfirmationDialogMessage",
|
||||
"moveUndatedConfirmationDialogSetDate",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsConfirmationDialogMoveUndatedItems",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -86,7 +107,10 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"moveUndatedConfirmationDialogMessage",
|
||||
"moveUndatedConfirmationDialogSetDate",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsConfirmationDialogMoveUndatedItems",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
|
Loading…
Reference in a new issue