diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d71b62195..a515a5fd5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -56,6 +56,7 @@ "resetButtonTooltip": "Reset", "doubleBackExitMessage": "Tap “back” again to exit.", + "doNotAskAgain": "Do not ask again", "sourceStateLoading": "Loading", "sourceStateCataloguing": "Cataloguing", @@ -569,6 +570,11 @@ "settingsKeepScreenOnTitle": "Keep Screen On", "settingsDoubleBackExit": "Tap “back” twice to exit", + "settingsConfirmationDialogTile": "Confirmation dialogs", + "settingsConfirmationDialogTitle": "Confirmation Dialogs", + "settingsConfirmationDialogDeleteItems": "Ask before deleting items forever", + "settingsConfirmationDialogMoveToBinItems": "Ask before moving items to the recycle bin", + "settingsNavigationDrawerTile": "Navigation menu", "settingsNavigationDrawerEditorTitle": "Navigation Menu", "settingsNavigationDrawerBanner": "Touch and hold to move and reorder menu items.", diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index bde23b6fa..e3c2de204 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -16,12 +16,13 @@ class SettingsDefaults { static const canUseAnalysisService = true; static const isInstalledAppAccessAllowed = false; static const isErrorReportingAllowed = false; + static const tileLayout = TileLayout.grid; + + // navigation static const mustBackTwiceToExit = true; static const keepScreenOn = KeepScreenOn.viewerOnly; static const homePage = HomePageSetting.collection; - static const tileLayout = TileLayout.grid; - - // drawer + static const confirmationDialogs = ConfirmationDialog.values; static final drawerTypeBookmarks = [ null, MimeFilter.video, diff --git a/lib/model/settings/enums/confirmation_dialogs.dart b/lib/model/settings/enums/confirmation_dialogs.dart new file mode 100644 index 000000000..4f1a58742 --- /dev/null +++ b/lib/model/settings/enums/confirmation_dialogs.dart @@ -0,0 +1,15 @@ +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; + } + } +} diff --git a/lib/model/settings/enums/enums.dart b/lib/model/settings/enums/enums.dart index f4678d208..8957d0141 100644 --- a/lib/model/settings/enums/enums.dart +++ b/lib/model/settings/enums/enums.dart @@ -1,16 +1,18 @@ -enum CoordinateFormat { dms, decimal } - enum AccessibilityAnimations { system, disabled, enabled } enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 } -enum EntryBackground { black, white, checkered } +enum ConfirmationDialog { delete, moveToBin } -enum HomePageSetting { collection, albums } +enum CoordinateFormat { dms, decimal } + +enum EntryBackground { black, white, checkered } // browse providers at https://leaflet-extras.github.io/leaflet-providers/preview/ enum EntryMapStyle { googleNormal, googleHybrid, googleTerrain, osmHot, stamenToner, stamenWatercolor } +enum HomePageSetting { collection, albums } + enum KeepScreenOn { never, viewerOnly, always } enum UnitSystem { metric, imperial } diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 7643136ac..a3bdc638d 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -42,15 +42,16 @@ class Settings extends ChangeNotifier { static const isInstalledAppAccessAllowedKey = 'is_installed_app_access_allowed'; static const isErrorReportingAllowedKey = 'is_crashlytics_enabled'; static const localeKey = 'locale'; - static const mustBackTwiceToExitKey = 'must_back_twice_to_exit'; - static const keepScreenOnKey = 'keep_screen_on'; - static const homePageKey = 'home_page'; static const catalogTimeZoneKey = 'catalog_time_zone'; static const tileExtentPrefixKey = 'tile_extent_'; static const tileLayoutPrefixKey = 'tile_layout_'; static const topEntryIdsKey = 'top_entry_ids'; - // drawer + // navigation + 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 drawerTypeBookmarksKey = 'drawer_type_bookmarks'; static const drawerAlbumBookmarksKey = 'drawer_album_bookmarks'; static const drawerPageBookmarksKey = 'drawer_page_bookmarks'; @@ -236,18 +237,6 @@ class Settings extends ChangeNotifier { return _appliedLocale!; } - bool get mustBackTwiceToExit => getBoolOrDefault(mustBackTwiceToExitKey, SettingsDefaults.mustBackTwiceToExit); - - set mustBackTwiceToExit(bool newValue) => setAndNotify(mustBackTwiceToExitKey, newValue); - - KeepScreenOn get keepScreenOn => getEnumOrDefault(keepScreenOnKey, SettingsDefaults.keepScreenOn, KeepScreenOn.values); - - set keepScreenOn(KeepScreenOn newValue) => setAndNotify(keepScreenOnKey, newValue.toString()); - - HomePageSetting get homePage => getEnumOrDefault(homePageKey, SettingsDefaults.homePage, HomePageSetting.values); - - set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString()); - String get catalogTimeZone => getString(catalogTimeZoneKey) ?? ''; set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue); @@ -264,7 +253,23 @@ class Settings extends ChangeNotifier { set topEntryIds(List? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList()); - // drawer + // navigation + + bool get mustBackTwiceToExit => getBoolOrDefault(mustBackTwiceToExitKey, SettingsDefaults.mustBackTwiceToExit); + + set mustBackTwiceToExit(bool newValue) => setAndNotify(mustBackTwiceToExitKey, newValue); + + KeepScreenOn get keepScreenOn => getEnumOrDefault(keepScreenOnKey, SettingsDefaults.keepScreenOn, KeepScreenOn.values); + + set keepScreenOn(KeepScreenOn newValue) => setAndNotify(keepScreenOnKey, newValue.toString()); + + HomePageSetting get homePage => getEnumOrDefault(homePageKey, SettingsDefaults.homePage, HomePageSetting.values); + + set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString()); + + List get confirmationDialogs => getEnumListOrDefault(confirmationDialogsKey, SettingsDefaults.confirmationDialogs, ConfirmationDialog.values); + + set confirmationDialogs(List newValue) => setAndNotify(confirmationDialogsKey, newValue.map((v) => v.toString()).toList()); List get drawerTypeBookmarks => (getStringList(drawerTypeBookmarksKey))?.map((v) { @@ -662,6 +667,7 @@ class Settings extends ChangeNotifier { debugPrint('failed to import key=$key, value=$value is not a string'); } break; + case confirmationDialogsKey: case drawerTypeBookmarksKey: case drawerAlbumBookmarksKey: case drawerPageBookmarksKey: diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 6e38e1f22..6c6f8766b 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -10,6 +10,7 @@ import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -24,6 +25,7 @@ import 'package:aves/widgets/common/action_mixins/permission_aware.dart'; import 'package:aves/widgets/common/action_mixins/size_aware.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart'; +import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/map/map_page.dart'; import 'package:aves/widgets/search/search_delegate.dart'; @@ -262,25 +264,12 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware final selectionDirs = entries.map((e) => e.directory).whereNotNull().toSet(); final todoCount = entries.length; - final confirmed = await showDialog( + if (!(await showConfirmationDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(l10n.deleteEntriesConfirmationDialogMessage(todoCount)), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(l10n.deleteButtonLabel), - ), - ], - ); - }, - ); - if (confirmed == null || !confirmed) return; + type: ConfirmationDialog.delete, + message: l10n.deleteEntriesConfirmationDialogMessage(todoCount), + confirmationButtonLabel: l10n.deleteButtonLabel, + ))) return; if (!pureTrash && !await checkStoragePermissionForAlbums(context, selectionDirs, entries: entries)) return; diff --git a/lib/widgets/common/action_mixins/entry_storage.dart b/lib/widgets/common/action_mixins/entry_storage.dart index c175c0e2a..c53ab8247 100644 --- a/lib/widgets/common/action_mixins/entry_storage.dart +++ b/lib/widgets/common/action_mixins/entry_storage.dart @@ -7,6 +7,7 @@ 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/settings/enums/enums.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'; @@ -19,7 +20,7 @@ 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'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/filter_grids/album_pick.dart'; import 'package:collection/collection.dart'; @@ -41,25 +42,12 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { final l10n = context.l10n; if (toBin) { - final confirmed = await showDialog( + if (!(await showConfirmationDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(l10n.binEntriesConfirmationDialogMessage(todoCount)), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(l10n.deleteButtonLabel), - ), - ], - ); - }, - ); - if (confirmed == null || !confirmed) return; + type: ConfirmationDialog.moveToBin, + message: l10n.binEntriesConfirmationDialogMessage(todoCount), + confirmationButtonLabel: l10n.deleteButtonLabel, + ))) return; } final source = context.read(); diff --git a/lib/widgets/dialogs/aves_confirmation_dialog.dart b/lib/widgets/dialogs/aves_confirmation_dialog.dart new file mode 100644 index 000000000..6d51c812e --- /dev/null +++ b/lib/widgets/dialogs/aves_confirmation_dialog.dart @@ -0,0 +1,79 @@ +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:flutter/material.dart'; + +import 'aves_dialog.dart'; + +Future showConfirmationDialog({ + required BuildContext context, + required ConfirmationDialog type, + required String message, + required String confirmationButtonLabel, +}) async { + if (!settings.confirmationDialogs.contains(type)) return true; + + final confirmed = await showDialog( + context: context, + builder: (context) => AvesConfirmationDialog( + type: type, + message: message, + confirmationButtonLabel: confirmationButtonLabel, + ), + ); + return confirmed == true; +} + +class AvesConfirmationDialog extends StatefulWidget { + final ConfirmationDialog type; + final String message, confirmationButtonLabel; + + const AvesConfirmationDialog({ + Key? key, + required this.type, + required this.message, + required this.confirmationButtonLabel, + }) : super(key: key); + + @override + _AvesConfirmationDialogState createState() => _AvesConfirmationDialogState(); +} + +class _AvesConfirmationDialogState extends State { + final ValueNotifier _skipConfirmation = 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), + ), + ValueListenableBuilder( + valueListenable: _skipConfirmation, + builder: (context, ask, child) => SwitchListTile( + value: ask, + onChanged: (v) => _skipConfirmation.value = v, + title: Text(context.l10n.doNotAskAgain), + ), + ), + ], + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + ), + TextButton( + onPressed: () { + if (_skipConfirmation.value) { + settings.confirmationDialogs = settings.confirmationDialogs.toList()..remove(widget.type); + } + Navigator.pop(context, true); + }, + child: Text(widget.confirmationButtonLabel), + ), + ], + ); + } +} diff --git a/lib/widgets/settings/common/quick_actions/editor_page.dart b/lib/widgets/settings/common/quick_actions/editor_page.dart index e3d87797a..874441321 100644 --- a/lib/widgets/settings/common/quick_actions/editor_page.dart +++ b/lib/widgets/settings/common/quick_actions/editor_page.dart @@ -97,7 +97,7 @@ class _QuickActionEditorBodyState extends State const ConfirmationDialogPage(), + ), + ); + }, + ); + } +} + +class ConfirmationDialogPage extends StatelessWidget { + static const routeName = '/settings/navigation_confirmation'; + + const ConfirmationDialogPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.l10n.settingsConfirmationDialogTitle), + ), + body: SafeArea( + child: Selector>( + 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(), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/settings/navigation/navigation.dart b/lib/widgets/settings/navigation/navigation.dart index 57c6c5c51..15ed104e6 100644 --- a/lib/widgets/settings/navigation/navigation.dart +++ b/lib/widgets/settings/navigation/navigation.dart @@ -8,6 +8,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart'; +import 'package:aves/widgets/settings/navigation/confirmation_dialogs.dart'; import 'package:aves/widgets/settings/navigation/drawer.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -53,6 +54,7 @@ class NavigationSection extends StatelessWidget { }, ), const NavigationDrawerTile(), + const ConfirmationDialogTile(), ListTile( title: Text(context.l10n.settingsKeepScreenOnTile), subtitle: Text(currentKeepScreenOn.getName(context)), diff --git a/lib/widgets/settings/thumbnails/collection_actions_editor.dart b/lib/widgets/settings/thumbnails/collection_actions_editor.dart index 79c4594dc..05da541a0 100644 --- a/lib/widgets/settings/thumbnails/collection_actions_editor.dart +++ b/lib/widgets/settings/thumbnails/collection_actions_editor.dart @@ -42,7 +42,7 @@ class CollectionActionEditorPage extends StatelessWidget { allAvailableActions: EntrySetActions.browsing, actionIcon: (action) => action.getIcon(), actionText: (context, action) => action.getText(context), - load: () => settings.collectionBrowsingQuickActions.toList(), + load: () => settings.collectionBrowsingQuickActions, save: (actions) => settings.collectionBrowsingQuickActions = actions, ), ), @@ -53,7 +53,7 @@ class CollectionActionEditorPage extends StatelessWidget { allAvailableActions: EntrySetActions.selection, actionIcon: (action) => action.getIcon(), actionText: (context, action) => action.getText(context), - load: () => settings.collectionSelectionQuickActions.toList(), + load: () => settings.collectionSelectionQuickActions, save: (actions) => settings.collectionSelectionQuickActions = actions, ), ), diff --git a/lib/widgets/settings/video/video_actions_editor.dart b/lib/widgets/settings/video/video_actions_editor.dart index d0429ffa2..f52cd5ab4 100644 --- a/lib/widgets/settings/video/video_actions_editor.dart +++ b/lib/widgets/settings/video/video_actions_editor.dart @@ -37,7 +37,7 @@ class VideoActionEditorPage extends StatelessWidget { allAvailableActions: VideoActions.all, actionIcon: (action) => action.getIcon(), actionText: (context, action) => action.getText(context), - load: () => settings.videoQuickActions.toList(), + load: () => settings.videoQuickActions, save: (actions) => settings.videoQuickActions = actions, ); } diff --git a/lib/widgets/settings/viewer/viewer_actions_editor.dart b/lib/widgets/settings/viewer/viewer_actions_editor.dart index 17018e0df..88b1859d6 100644 --- a/lib/widgets/settings/viewer/viewer_actions_editor.dart +++ b/lib/widgets/settings/viewer/viewer_actions_editor.dart @@ -44,7 +44,7 @@ class ViewerActionEditorPage extends StatelessWidget { allAvailableActions: allAvailableActions, actionIcon: (action) => action.getIcon(), actionText: (context, action) => action.getText(context), - load: () => settings.viewerQuickActions.toList(), + load: () => settings.viewerQuickActions, save: (actions) => settings.viewerQuickActions = actions, ); } diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index 8e98a8d4f..48ef7bd9f 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -9,6 +9,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_metadata_edition.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/highlight.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'; @@ -24,6 +25,7 @@ import 'package:aves/widgets/common/action_mixins/permission_aware.dart'; import 'package:aves/widgets/common/action_mixins/size_aware.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart'; +import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/entry_editors/rename_dialog.dart'; import 'package:aves/widgets/dialogs/export_entry_dialog.dart'; @@ -173,25 +175,12 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } final l10n = context.l10n; - final confirmed = await showDialog( + if (!(await showConfirmationDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(l10n.deleteEntriesConfirmationDialogMessage(1)), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(l10n.deleteButtonLabel), - ), - ], - ); - }, - ); - if (confirmed == null || !confirmed) return; + type: ConfirmationDialog.delete, + message: l10n.deleteEntriesConfirmationDialogMessage(1), + confirmationButtonLabel: l10n.deleteButtonLabel, + ))) return; if (!await checkStoragePermission(context, {entry})) return; diff --git a/untranslated.json b/untranslated.json index cfa5917f9..224f209ba 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1,47 +1,83 @@ { "de": [ "timeDays", + "doNotAskAgain", "entryActionConvert", "entryActionRestore", "filterBinLabel", "binEntriesConfirmationDialogMessage", "collectionActionEmptyBin", "binPageTitle", + "settingsConfirmationDialogTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationDialogDeleteItems", + "settingsConfirmationDialogMoveToBinItems", "settingsEnableBin", "settingsEnableBinSubtitle" ], "es": [ "timeDays", + "doNotAskAgain", "entryActionRestore", "filterBinLabel", "binEntriesConfirmationDialogMessage", "collectionActionEmptyBin", "binPageTitle", + "settingsConfirmationDialogTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationDialogDeleteItems", + "settingsConfirmationDialogMoveToBinItems", "settingsEnableBin", "settingsEnableBinSubtitle" ], + "fr": [ + "doNotAskAgain", + "settingsConfirmationDialogTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationDialogDeleteItems", + "settingsConfirmationDialogMoveToBinItems" + ], + + "ko": [ + "doNotAskAgain", + "settingsConfirmationDialogTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationDialogDeleteItems", + "settingsConfirmationDialogMoveToBinItems" + ], + "pt": [ "timeDays", + "doNotAskAgain", "entryActionConvert", "entryActionRestore", "filterBinLabel", "binEntriesConfirmationDialogMessage", "collectionActionEmptyBin", "binPageTitle", + "settingsConfirmationDialogTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationDialogDeleteItems", + "settingsConfirmationDialogMoveToBinItems", "settingsEnableBin", "settingsEnableBinSubtitle" ], "ru": [ "timeDays", + "doNotAskAgain", "entryActionConvert", "entryActionRestore", "filterBinLabel", "binEntriesConfirmationDialogMessage", "collectionActionEmptyBin", "binPageTitle", + "settingsConfirmationDialogTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationDialogDeleteItems", + "settingsConfirmationDialogMoveToBinItems", "settingsEnableBin", "settingsEnableBinSubtitle" ]