#562 sony burst support

This commit is contained in:
Thibault Deckers 2023-03-22 15:54:42 +01:00
parent 708f1310e4
commit 5b495e95e5
20 changed files with 507 additions and 181 deletions

View file

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Added ### Added
- Collection: optional support for Samsung and Sony burst patterns
- Info: improved state/place display (requires rescan, limited to AU/GB/EN) - Info: improved state/place display (requires rescan, limited to AU/GB/EN)
- improved support for system font scale - improved support for system font scale

View file

@ -754,6 +754,9 @@
"settingsCollectionBrowsingQuickActionEditorBanner": "Touch and hold to move buttons and select which actions are displayed when browsing items.", "settingsCollectionBrowsingQuickActionEditorBanner": "Touch and hold to move buttons and select which actions are displayed when browsing items.",
"settingsCollectionSelectionQuickActionEditorBanner": "Touch and hold to move buttons and select which actions are displayed when selecting items.", "settingsCollectionSelectionQuickActionEditorBanner": "Touch and hold to move buttons and select which actions are displayed when selecting items.",
"settingsCollectionBurstPatternsTile": "Burst patterns",
"settingsCollectionBurstPatternsNone": "None",
"settingsViewerSectionTitle": "Viewer", "settingsViewerSectionTitle": "Viewer",
"settingsViewerGestureSideTapNext": "Tap on screen edges to show previous/next item", "settingsViewerGestureSideTapNext": "Tap on screen edges to show previous/next item",
"settingsViewerUseCutout": "Use cutout area", "settingsViewerUseCutout": "Use cutout area",

View file

@ -7,8 +7,6 @@ import 'package:aves/services/common/services.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
extension ExtraAvesEntryMultipage on AvesEntry { extension ExtraAvesEntryMultipage on AvesEntry {
static final _burstFilenamePattern = RegExp(r'^(\d{8}_\d{6})_(\d+)$');
bool get isMultiPage => (catalogMetadata?.isMultiPage ?? false) || isBurst; bool get isMultiPage => (catalogMetadata?.isMultiPage ?? false) || isBurst;
bool get isBurst => burstEntries?.isNotEmpty == true; bool get isBurst => burstEntries?.isNotEmpty == true;
@ -18,11 +16,13 @@ extension ExtraAvesEntryMultipage on AvesEntry {
bool get isMotionPhoto => (catalogMetadata?.isMotionPhoto ?? false) || _isMotionPhotoLegacy; bool get isMotionPhoto => (catalogMetadata?.isMotionPhoto ?? false) || _isMotionPhotoLegacy;
String? get burstKey { String? getBurstKey(List<String> patterns) {
if (filenameWithoutExtension != null) { if (filenameWithoutExtension != null) {
final match = _burstFilenamePattern.firstMatch(filenameWithoutExtension!); for (final pattern in patterns) {
if (match != null) { final match = RegExp(pattern).firstMatch(filenameWithoutExtension!);
return '$directory/${match.group(1)}'; if (match != null) {
return '$directory/${match.group(1)}';
}
} }
} }
return null; return null;

View file

@ -13,8 +13,8 @@ import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/enums/enums.dart';
import 'package:aves/ref/bursts.dart';
import 'package:aves/services/accessibility_service.dart'; import 'package:aves/services/accessibility_service.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/common/search/page.dart';
@ -23,7 +23,9 @@ import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/places_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart';
import 'package:aves_map/aves_map.dart'; import 'package:aves_map/aves_map.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
@ -90,6 +92,7 @@ class Settings extends ChangeNotifier {
static const drawerPageBookmarksKey = 'drawer_page_bookmarks'; static const drawerPageBookmarksKey = 'drawer_page_bookmarks';
// collection // collection
static const collectionBurstPatternsKey = 'collection_burst_patterns';
static const collectionGroupFactorKey = 'collection_group_factor'; static const collectionGroupFactorKey = 'collection_group_factor';
static const collectionSortFactorKey = 'collection_sort_factor'; static const collectionSortFactorKey = 'collection_sort_factor';
static const collectionSortReverseKey = 'collection_sort_reverse'; static const collectionSortReverseKey = 'collection_sort_reverse';
@ -245,6 +248,10 @@ class Settings extends ChangeNotifier {
final performanceClass = await deviceService.getPerformanceClass(); final performanceClass = await deviceService.getPerformanceClass();
enableBlurEffect = performanceClass >= 29; enableBlurEffect = performanceClass >= 29;
final androidInfo = await DeviceInfoPlugin().androidInfo;
final pattern = BurstPatterns.byManufacturer[androidInfo.manufacturer];
collectionBurstPatterns = pattern != null ? [pattern] : [];
// availability // availability
if (flavor.hasMapStyleDefault) { if (flavor.hasMapStyleDefault) {
final defaultMapStyle = mobileServices.defaultMapStyle; final defaultMapStyle = mobileServices.defaultMapStyle;
@ -495,6 +502,10 @@ class Settings extends ChangeNotifier {
// collection // collection
List<String> get collectionBurstPatterns => getStringList(collectionBurstPatternsKey) ?? [];
set collectionBurstPatterns(List<String> newValue) => _set(collectionBurstPatternsKey, newValue);
EntryGroupFactor get collectionSectionFactor => getEnumOrDefault(collectionGroupFactorKey, SettingsDefaults.collectionSectionFactor, EntryGroupFactor.values); EntryGroupFactor get collectionSectionFactor => getEnumOrDefault(collectionGroupFactorKey, SettingsDefaults.collectionSectionFactor, EntryGroupFactor.values);
set collectionSectionFactor(EntryGroupFactor newValue) => _set(collectionGroupFactorKey, newValue.toString()); set collectionSectionFactor(EntryGroupFactor newValue) => _set(collectionGroupFactorKey, newValue.toString());
@ -1152,6 +1163,7 @@ class Settings extends ChangeNotifier {
case drawerTypeBookmarksKey: case drawerTypeBookmarksKey:
case drawerAlbumBookmarksKey: case drawerAlbumBookmarksKey:
case drawerPageBookmarksKey: case drawerPageBookmarksKey:
case collectionBurstPatternsKey:
case pinnedFiltersKey: case pinnedFiltersKey:
case hiddenFiltersKey: case hiddenFiltersKey:
case collectionBrowsingQuickActionsKey: case collectionBrowsingQuickActionsKey:

View file

@ -28,6 +28,7 @@ import 'package:flutter/foundation.dart';
class CollectionLens with ChangeNotifier { class CollectionLens with ChangeNotifier {
final CollectionSource source; final CollectionSource source;
final Set<CollectionFilter> filters; final Set<CollectionFilter> filters;
List<String> burstPatterns;
EntryGroupFactor sectionFactor; EntryGroupFactor sectionFactor;
EntrySortFactor sortFactor; EntrySortFactor sortFactor;
bool sortReverse; bool sortReverse;
@ -50,6 +51,7 @@ class CollectionLens with ChangeNotifier {
this.fixedSort = false, this.fixedSort = false,
this.fixedSelection, this.fixedSelection,
}) : filters = (filters ?? {}).whereNotNull().toSet(), }) : filters = (filters ?? {}).whereNotNull().toSet(),
burstPatterns = settings.collectionBurstPatterns,
sectionFactor = settings.collectionSectionFactor, sectionFactor = settings.collectionSectionFactor,
sortFactor = settings.collectionSortFactor, sortFactor = settings.collectionSortFactor,
sortReverse = settings.collectionSortReverse { sortReverse = settings.collectionSortReverse {
@ -85,6 +87,7 @@ class CollectionLens with ChangeNotifier {
} }
_subscriptions.add(settings.updateStream _subscriptions.add(settings.updateStream
.where((event) => [ .where((event) => [
Settings.collectionBurstPatternsKey,
Settings.collectionSortFactorKey, Settings.collectionSortFactorKey,
Settings.collectionGroupFactorKey, Settings.collectionGroupFactorKey,
Settings.collectionSortReverseKey, Settings.collectionSortReverseKey,
@ -188,7 +191,7 @@ class CollectionLens with ChangeNotifier {
} }
void _groupBursts() { void _groupBursts() {
final byBurstKey = groupBy<AvesEntry, String?>(_filteredSortedEntries, (entry) => entry.burstKey).whereNotNullKey(); final byBurstKey = groupBy<AvesEntry, String?>(_filteredSortedEntries, (entry) => entry.getBurstKey(burstPatterns)).whereNotNullKey();
byBurstKey.forEach((burstKey, entries) { byBurstKey.forEach((burstKey, entries) {
if (entries.length > 1) { if (entries.length > 1) {
entries.sort(AvesEntrySort.compareByName); entries.sort(AvesEntrySort.compareByName);
@ -287,13 +290,19 @@ class CollectionLens with ChangeNotifier {
} }
void _onSettingsChanged() { void _onSettingsChanged() {
final newBurstPatterns = settings.collectionBurstPatterns;
final newSortFactor = settings.collectionSortFactor; final newSortFactor = settings.collectionSortFactor;
final newSectionFactor = settings.collectionSectionFactor; final newSectionFactor = settings.collectionSectionFactor;
final newSortReverse = settings.collectionSortReverse; final newSortReverse = settings.collectionSortReverse;
final needSort = sortFactor != newSortFactor || sortReverse != newSortReverse; final needFilter = burstPatterns != newBurstPatterns;
final needSort = needFilter || sortFactor != newSortFactor || sortReverse != newSortReverse;
final needSection = needSort || sectionFactor != newSectionFactor; final needSection = needSort || sectionFactor != newSectionFactor;
if (needFilter) {
burstPatterns = newBurstPatterns;
_applyFilters();
}
if (needSort) { if (needSort) {
sortFactor = newSortFactor; sortFactor = newSortFactor;
sortReverse = newSortReverse; sortReverse = newSortReverse;
@ -303,6 +312,10 @@ class CollectionLens with ChangeNotifier {
sectionFactor = newSectionFactor; sectionFactor = newSectionFactor;
_applySection(); _applySection();
} }
if (needFilter) {
filterChangeNotifier.notifyListeners();
}
if (needSort || needSection) { if (needSort || needSection) {
sortSectionChangeNotifier.notifyListeners(); sortSectionChangeNotifier.notifyListeners();
} }
@ -316,9 +329,9 @@ class CollectionLens with ChangeNotifier {
if (groupBursts) { if (groupBursts) {
// find impacted burst groups // find impacted burst groups
final obsoleteBurstEntries = <AvesEntry>{}; final obsoleteBurstEntries = <AvesEntry>{};
final burstKeys = entries.map((entry) => entry.burstKey).whereNotNull().toSet(); final burstKeys = entries.map((entry) => entry.getBurstKey(burstPatterns)).whereNotNull().toSet();
if (burstKeys.isNotEmpty) { if (burstKeys.isNotEmpty) {
_filteredSortedEntries.where((entry) => entry.isBurst && burstKeys.contains(entry.burstKey)).forEach((mainEntry) { _filteredSortedEntries.where((entry) => entry.isBurst && burstKeys.contains(entry.getBurstKey(burstPatterns))).forEach((mainEntry) {
final subEntries = mainEntry.burstEntries!; final subEntries = mainEntry.burstEntries!;
// remove the deleted sub-entries // remove the deleted sub-entries
subEntries.removeWhere(entries.contains); subEntries.removeWhere(entries.contains);

42
lib/ref/bursts.dart Normal file
View file

@ -0,0 +1,42 @@
class BurstPatterns {
static const samsung = r'^(\d{8}_\d{6})_(\d+)$';
static const sony = r'^DSC_\d+_BURST(\d{17})(_COVER)?$';
static final options = [
BurstPatterns.samsung,
BurstPatterns.sony,
];
static String getName(String pattern) {
switch (pattern) {
case samsung:
return 'Samsung';
case sony:
return 'Sony';
default:
return pattern;
}
}
static String getExample(String pattern) {
switch (pattern) {
case samsung:
return '20151021_072800_007';
case sony:
return 'DSC_0007_BURST20151021072800123';
default:
return '?';
}
}
static const byManufacturer = {
_Manufacturers.samsung: samsung,
_Manufacturers.sony: sony,
};
}
// values as returned by `DeviceInfoPlugin().androidInfo`
class _Manufacturers {
static const samsung = 'samsung';
static const sony = 'sony';
}

View file

@ -4,6 +4,7 @@ import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/format.dart'; import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/utils/collection_utils.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/utils/file_utils.dart'; import 'package:aves/utils/file_utils.dart';
import 'package:aves/widgets/collection/grid/list_details_theme.dart'; import 'package:aves/widgets/collection/grid/list_details_theme.dart';
@ -80,7 +81,7 @@ class EntryListDetails extends StatelessWidget {
final date = entry.bestDate; final date = entry.bestDate;
final dateText = date != null ? formatDateTime(date, locale, use24hour) : Constants.overlayUnknown; final dateText = date != null ? formatDateTime(date, locale, use24hour) : Constants.overlayUnknown;
final size = entry.sizeBytes; final size = entry.burstEntries?.map((v) => v.sizeBytes).sum ?? entry.sizeBytes;
final sizeText = size != null ? formatFileSize(locale, size) : Constants.overlayUnknown; final sizeText = size != null ? formatFileSize(locale, size) : Constants.overlayUnknown;
return _buildRow( return _buildRow(

View file

@ -29,9 +29,9 @@ 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/action_mixins/size_aware.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:aves/widgets/dialogs/convert_entry_dialog.dart'; import 'package:aves/widgets/dialogs/convert_entry_dialog.dart';
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart'; import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -172,13 +172,13 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
if (uniqueNames.length < names.length) { if (uniqueNames.length < names.length) {
final value = await showDialog<NameConflictStrategy>( final value = await showDialog<NameConflictStrategy>(
context: context, context: context,
builder: (context) => AvesSelectionDialog<NameConflictStrategy>( builder: (context) => AvesSingleSelectionDialog<NameConflictStrategy>(
initialValue: nameConflictStrategy, initialValue: nameConflictStrategy,
options: Map.fromEntries(NameConflictStrategy.values.map((v) => MapEntry(v, v.getName(context)))), options: Map.fromEntries(NameConflictStrategy.values.map((v) => MapEntry(v, v.getName(context)))),
message: originAlbums.length == 1 ? l10n.nameConflictDialogSingleSourceMessage : l10n.nameConflictDialogMultipleSourceMessage, message: originAlbums.length == 1 ? l10n.nameConflictDialogSingleSourceMessage : l10n.nameConflictDialogMultipleSourceMessage,
confirmationButtonLabel: l10n.continueButtonLabel, confirmationButtonLabel: l10n.continueButtonLabel,
), ),
routeSettings: const RouteSettings(name: AvesSelectionDialog.routeName), routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
); );
if (value == null) return; if (value == null) return;
nameConflictStrategy = value; nameConflictStrategy = value;

View file

@ -21,7 +21,8 @@ import 'package:aves/widgets/common/map/buttons/panel.dart';
import 'package:aves/widgets/common/map/decorator.dart'; import 'package:aves/widgets/common/map/decorator.dart';
import 'package:aves/widgets/common/map/leaflet/map.dart'; import 'package:aves/widgets/common/map/leaflet/map.dart';
import 'package:aves/widgets/common/thumbnail/image.dart'; import 'package:aves/widgets/common/thumbnail/image.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
import 'package:aves_map/aves_map.dart'; import 'package:aves_map/aves_map.dart';
import 'package:aves_utils/aves_utils.dart'; import 'package:aves_utils/aves_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -212,7 +213,7 @@ class _GeoMapState extends State<GeoMap> {
child: OverlayTextButton( child: OverlayTextButton(
onPressed: () => showSelectionDialog<EntryMapStyle>( onPressed: () => showSelectionDialog<EntryMapStyle>(
context: context, context: context,
builder: (context) => AvesSelectionDialog<EntryMapStyle?>( builder: (context) => AvesSingleSelectionDialog<EntryMapStyle?>(
initialValue: settings.mapStyle, initialValue: settings.mapStyle,
options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))), options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.mapStyleDialogTitle, title: context.l10n.mapStyleDialogTitle,

View file

@ -3,7 +3,8 @@ import 'package:aves/model/settings/enums/l10n.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
import 'package:aves_map/aves_map.dart'; import 'package:aves_map/aves_map.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -18,7 +19,7 @@ class MapActionDelegate {
case MapAction.selectStyle: case MapAction.selectStyle:
showSelectionDialog<EntryMapStyle>( showSelectionDialog<EntryMapStyle>(
context: context, context: context,
builder: (context) => AvesSelectionDialog<EntryMapStyle?>( builder: (context) => AvesSingleSelectionDialog<EntryMapStyle?>(
initialValue: settings.mapStyle, initialValue: settings.mapStyle,
options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))), options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.mapStyleDialogTitle, title: context.l10n.mapStyleDialogTitle,

View file

@ -1,154 +0,0 @@
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/basic/list_tiles/reselectable_radio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'aves_dialog.dart';
Future<void> showSelectionDialog<T>({
required BuildContext context,
required WidgetBuilder builder,
required void Function(T value) onSelection,
}) async {
final value = await showDialog<T>(
context: context,
builder: builder,
routeSettings: const RouteSettings(name: AvesSelectionDialog.routeName),
);
// wait for the dialog to hide as applying the change may block the UI
await Future.delayed(Durations.dialogTransitionAnimation * timeDilation);
if (value != null) {
onSelection(value);
}
}
typedef TextBuilder<T> = String Function(T value);
class AvesSelectionDialog<T> extends StatefulWidget {
static const routeName = '/dialog/selection';
final T initialValue;
final Map<T, String> options;
final TextBuilder<T>? optionSubtitleBuilder;
final String? title, message, confirmationButtonLabel;
final bool? dense;
const AvesSelectionDialog({
super.key,
required this.initialValue,
required this.options,
this.optionSubtitleBuilder,
this.title,
this.message,
this.confirmationButtonLabel,
this.dense,
});
@override
State<AvesSelectionDialog<T>> createState() => _AvesSelectionDialogState<T>();
}
class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog<T>> {
late T _selectedValue;
@override
void initState() {
super.initState();
_selectedValue = widget.initialValue;
}
@override
Widget build(BuildContext context) {
final title = widget.title;
final message = widget.message;
final verticalPadding = (title == null && message == null) ? AvesDialog.cornerRadius.y / 2 : .0;
final confirmationButtonLabel = widget.confirmationButtonLabel;
final needConfirmation = confirmationButtonLabel != null;
return AvesDialog(
title: title,
scrollableContent: [
if (verticalPadding != 0) SizedBox(height: verticalPadding),
if (message != null)
Padding(
padding: const EdgeInsets.all(16),
child: Text(message),
),
...widget.options.entries.map((kv) {
final radioValue = kv.key;
final radioTitle = kv.value;
return SelectionRadioListTile(
value: radioValue,
title: radioTitle,
optionSubtitleBuilder: widget.optionSubtitleBuilder,
needConfirmation: needConfirmation,
dense: widget.dense,
getGroupValue: () => _selectedValue,
setGroupValue: (v) => setState(() => _selectedValue = v),
);
}),
if (verticalPadding != 0) SizedBox(height: verticalPadding),
],
actions: [
const CancelButton(),
if (needConfirmation)
TextButton(
onPressed: () => Navigator.maybeOf(context)?.pop(_selectedValue),
child: Text(confirmationButtonLabel),
),
],
);
}
}
class SelectionRadioListTile<T> extends StatelessWidget {
final T value;
final String title;
final TextBuilder<T>? optionSubtitleBuilder;
final bool needConfirmation;
final bool? dense;
final T Function() getGroupValue;
final void Function(T value) setGroupValue;
const SelectionRadioListTile({
super.key,
required this.value,
required this.title,
this.optionSubtitleBuilder,
required this.needConfirmation,
this.dense,
required this.getGroupValue,
required this.setGroupValue,
});
@override
Widget build(BuildContext context) {
final subtitle = optionSubtitleBuilder?.call(value);
return ReselectableRadioListTile<T>(
// key is expected by test driver
key: Key('$value'),
value: value,
groupValue: getGroupValue(),
onChanged: (v) {
if (needConfirmation) {
setGroupValue(v as T);
} else {
Navigator.maybeOf(context)?.pop(v);
}
},
reselectable: true,
title: Text(
title,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
subtitle: subtitle != null
? Text(
subtitle,
softWrap: false,
overflow: TextOverflow.fade,
)
: null,
dense: dense,
);
}
}

View file

@ -9,7 +9,8 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_caption.dart'; import 'package:aves/widgets/common/identity/aves_caption.dart';
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -105,7 +106,7 @@ class _EditVaultDialogState extends State<EditVaultDialog> {
_unfocus(); _unfocus();
showSelectionDialog<VaultLockType>( showSelectionDialog<VaultLockType>(
context: context, context: context,
builder: (context) => AvesSelectionDialog<VaultLockType>( builder: (context) => AvesSingleSelectionDialog<VaultLockType>(
initialValue: _lockType, initialValue: _lockType,
options: Map.fromEntries(_lockTypeOptions.map((v) => MapEntry(v, v.getText(context)))), options: Map.fromEntries(_lockTypeOptions.map((v) => MapEntry(v, v.getText(context)))),
), ),

View file

@ -0,0 +1,23 @@
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
Future<void> showSelectionDialog<T>({
required BuildContext context,
required WidgetBuilder builder,
required void Function(T value) onSelection,
}) async {
final value = await showDialog<T>(
context: context,
builder: builder,
routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
);
// wait for the dialog to hide as applying the change may block the UI
await Future.delayed(Durations.dialogTransitionAnimation * timeDilation);
if (value != null) {
onSelection(value);
}
}
typedef TextBuilder<T> = String Function(T value);

View file

@ -0,0 +1,91 @@
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
import 'package:flutter/material.dart';
class AvesMultiSelectionDialog<T> extends StatefulWidget {
static const routeName = '/dialog/multi_selection';
final Set<T> initialValue;
final Map<T, String> options;
final TextBuilder<T>? optionSubtitleBuilder;
final String? title, message;
final bool? dense;
const AvesMultiSelectionDialog({
super.key,
required this.initialValue,
required this.options,
this.optionSubtitleBuilder,
this.title,
this.message,
this.dense,
});
@override
State<AvesMultiSelectionDialog<T>> createState() => _AvesMultiSelectionDialogState<T>();
}
class _AvesMultiSelectionDialogState<T> extends State<AvesMultiSelectionDialog<T>> {
late Set<T> _selectedValues;
@override
void initState() {
super.initState();
_selectedValues = widget.initialValue;
}
@override
Widget build(BuildContext context) {
final title = widget.title;
final message = widget.message;
final verticalPadding = (title == null && message == null) ? AvesDialog.cornerRadius.y / 2 : .0;
return AvesDialog(
title: title,
scrollableContent: [
if (verticalPadding != 0) SizedBox(height: verticalPadding),
if (message != null)
Padding(
padding: const EdgeInsets.all(16),
child: Text(message),
),
...widget.options.entries.map((kv) {
final value = kv.key;
final title = kv.value;
final subtitle = widget.optionSubtitleBuilder?.call(value);
return SwitchListTile(
value: _selectedValues.contains(value),
onChanged: (v) {
if (v) {
_selectedValues.add(value);
} else {
_selectedValues.remove(value);
}
setState(() {});
},
title: Align(
alignment: Alignment.centerLeft,
child: Text(title),
),
subtitle: subtitle != null
? Text(
subtitle,
softWrap: false,
overflow: TextOverflow.fade,
)
: null,
dense: widget.dense,
);
}),
if (verticalPadding != 0) SizedBox(height: verticalPadding),
],
actions: [
const CancelButton(),
TextButton(
onPressed: () => Navigator.maybeOf(context)?.pop(widget.options.keys.where(_selectedValues.contains).toList()),
child: Text(context.l10n.applyButtonLabel),
),
],
);
}
}

View file

@ -0,0 +1,56 @@
import 'package:aves/widgets/common/basic/list_tiles/reselectable_radio.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
import 'package:flutter/material.dart';
class SelectionRadioListTile<T> extends StatelessWidget {
final T value;
final String title;
final TextBuilder<T>? optionSubtitleBuilder;
final bool needConfirmation;
final bool? dense;
final T Function() getGroupValue;
final void Function(T value) setGroupValue;
const SelectionRadioListTile({
super.key,
required this.value,
required this.title,
this.optionSubtitleBuilder,
required this.needConfirmation,
this.dense,
required this.getGroupValue,
required this.setGroupValue,
});
@override
Widget build(BuildContext context) {
final subtitle = optionSubtitleBuilder?.call(value);
return ReselectableRadioListTile<T>(
// key is expected by test driver
key: Key('$value'),
value: value,
groupValue: getGroupValue(),
onChanged: (v) {
if (needConfirmation) {
setGroupValue(v as T);
} else {
Navigator.maybeOf(context)?.pop(v);
}
},
reselectable: true,
title: Text(
title,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
subtitle: subtitle != null
? Text(
subtitle,
softWrap: false,
overflow: TextOverflow.fade,
)
: null,
dense: dense,
);
}
}

View file

@ -0,0 +1,80 @@
import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/radio_list_tile.dart';
import 'package:flutter/material.dart';
class AvesSingleSelectionDialog<T> extends StatefulWidget {
static const routeName = '/dialog/selection';
final T initialValue;
final Map<T, String> options;
final TextBuilder<T>? optionSubtitleBuilder;
final String? title, message, confirmationButtonLabel;
final bool? dense;
const AvesSingleSelectionDialog({
super.key,
required this.initialValue,
required this.options,
this.optionSubtitleBuilder,
this.title,
this.message,
this.confirmationButtonLabel,
this.dense,
});
@override
State<AvesSingleSelectionDialog<T>> createState() => _AvesSingleSelectionDialogState<T>();
}
class _AvesSingleSelectionDialogState<T> extends State<AvesSingleSelectionDialog<T>> {
late T _selectedValue;
@override
void initState() {
super.initState();
_selectedValue = widget.initialValue;
}
@override
Widget build(BuildContext context) {
final title = widget.title;
final message = widget.message;
final verticalPadding = (title == null && message == null) ? AvesDialog.cornerRadius.y / 2 : .0;
final confirmationButtonLabel = widget.confirmationButtonLabel;
final needConfirmation = confirmationButtonLabel != null;
return AvesDialog(
title: title,
scrollableContent: [
if (verticalPadding != 0) SizedBox(height: verticalPadding),
if (message != null)
Padding(
padding: const EdgeInsets.all(16),
child: Text(message),
),
...widget.options.entries.map((kv) {
final radioValue = kv.key;
final radioTitle = kv.value;
return SelectionRadioListTile(
value: radioValue,
title: radioTitle,
optionSubtitleBuilder: widget.optionSubtitleBuilder,
needConfirmation: needConfirmation,
dense: widget.dense,
getGroupValue: () => _selectedValue,
setGroupValue: (v) => setState(() => _selectedValue = v),
);
}),
if (verticalPadding != 0) SizedBox(height: verticalPadding),
],
actions: [
const CancelButton(),
if (needConfirmation)
TextButton(
onPressed: () => Navigator.maybeOf(context)?.pop(_selectedValue),
child: Text(confirmationButtonLabel),
),
],
);
}
}

View file

@ -1,12 +1,11 @@
import 'package:aves/model/device.dart'; import 'package:aves/model/device.dart';
import 'package:aves/model/wallpaper_target.dart'; import 'package:aves/model/wallpaper_target.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/radio_list_tile.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'aves_dialog.dart';
class WallpaperSettingsDialog extends StatefulWidget { class WallpaperSettingsDialog extends StatefulWidget {
static const routeName = '/dialog/wallpaper_settings'; static const routeName = '/dialog/wallpaper_settings';

View file

@ -1,10 +1,13 @@
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/time_utils.dart'; import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_caption.dart'; import 'package:aves/widgets/common/identity/aves_caption.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:aves/widgets/dialogs/duration_dialog.dart'; import 'package:aves/widgets/dialogs/duration_dialog.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/multi_selection.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -82,7 +85,7 @@ class SettingsSwitchListTile extends StatelessWidget {
} }
} }
class SettingsSelectionListTile<T extends Enum> extends StatelessWidget { class SettingsSelectionListTile<T> extends StatelessWidget {
final List<T> values; final List<T> values;
final String Function(BuildContext, T) getName; final String Function(BuildContext, T) getName;
final T Function(BuildContext, Settings) selector; final T Function(BuildContext, Settings) selector;
@ -123,7 +126,7 @@ class SettingsSelectionListTile<T extends Enum> extends StatelessWidget {
subtitle: AvesCaption(getName(context, current)), subtitle: AvesCaption(getName(context, current)),
onTap: () => showSelectionDialog<T>( onTap: () => showSelectionDialog<T>(
context: context, context: context,
builder: (context) => AvesSelectionDialog<T>( builder: (context) => AvesSingleSelectionDialog<T>(
initialValue: current, initialValue: current,
options: Map.fromEntries(values.map((v) => MapEntry(v, getName(context, v)))), options: Map.fromEntries(values.map((v) => MapEntry(v, getName(context, v)))),
optionSubtitleBuilder: optionSubtitleBuilder, optionSubtitleBuilder: optionSubtitleBuilder,
@ -137,6 +140,62 @@ class SettingsSelectionListTile<T extends Enum> extends StatelessWidget {
} }
} }
class SettingsMultiSelectionListTile<T> extends StatelessWidget {
final List<T> values;
final String Function(BuildContext, T) getName;
final List<T> Function(BuildContext, Settings) selector;
final ValueChanged<List<T>> onSelection;
final String tileTitle, noneSubtitle;
final WidgetBuilder? trailingBuilder;
final String? dialogTitle;
final TextBuilder<T>? optionSubtitleBuilder;
const SettingsMultiSelectionListTile({
super.key,
required this.values,
required this.getName,
required this.selector,
required this.onSelection,
required this.tileTitle,
required this.noneSubtitle,
this.trailingBuilder,
this.dialogTitle,
this.optionSubtitleBuilder,
});
@override
Widget build(BuildContext context) {
return Selector<Settings, List<T>>(
selector: selector,
builder: (context, current, child) {
Widget titleWidget = Text(tileTitle);
if (trailingBuilder != null) {
titleWidget = Row(
children: [
Expanded(child: titleWidget),
trailingBuilder!(context),
],
);
}
return ListTile(
title: titleWidget,
subtitle: AvesCaption(current.isEmpty ? noneSubtitle : current.map((v) => getName(context, v)).join(Constants.separator)),
onTap: () => showSelectionDialog<List<T>>(
context: context,
builder: (context) => AvesMultiSelectionDialog<T>(
initialValue: current.toSet(),
options: Map.fromEntries(values.map((v) => MapEntry(v, getName(context, v)))),
optionSubtitleBuilder: optionSubtitleBuilder,
title: dialogTitle,
),
onSelection: onSelection,
),
);
},
);
}
}
class SettingsDurationListTile extends StatelessWidget { class SettingsDurationListTile extends StatelessWidget {
final int Function(BuildContext, Settings) selector; final int Function(BuildContext, Settings) selector;
final ValueChanged<int> onChanged; final ValueChanged<int> onChanged;

View file

@ -1,4 +1,5 @@
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/ref/bursts.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
@ -27,6 +28,7 @@ class ThumbnailsSection extends SettingsSection {
List<SettingsTile> tiles(BuildContext context) => [ List<SettingsTile> tiles(BuildContext context) => [
if (!settings.useTvLayout) SettingsTileCollectionQuickActions(), if (!settings.useTvLayout) SettingsTileCollectionQuickActions(),
SettingsTileThumbnailOverlay(), SettingsTileThumbnailOverlay(),
SettingsTileBurstPatterns(),
]; ];
} }
@ -53,3 +55,19 @@ class SettingsTileThumbnailOverlay extends SettingsTile {
builder: (context) => const ThumbnailOverlayPage(), builder: (context) => const ThumbnailOverlayPage(),
); );
} }
class SettingsTileBurstPatterns extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsCollectionBurstPatternsTile;
@override
Widget build(BuildContext context) => SettingsMultiSelectionListTile<String>(
values: BurstPatterns.options,
getName: (context, v) => BurstPatterns.getName(v),
selector: (context, s) => s.collectionBurstPatterns,
onSelection: (v) => settings.collectionBurstPatterns = v,
tileTitle: title(context),
noneSubtitle: context.l10n.settingsCollectionBurstPatternsNone,
optionSubtitleBuilder: BurstPatterns.getExample,
);
}

View file

@ -443,6 +443,8 @@
"settingsCollectionQuickActionTabSelecting", "settingsCollectionQuickActionTabSelecting",
"settingsCollectionBrowsingQuickActionEditorBanner", "settingsCollectionBrowsingQuickActionEditorBanner",
"settingsCollectionSelectionQuickActionEditorBanner", "settingsCollectionSelectionQuickActionEditorBanner",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerSectionTitle", "settingsViewerSectionTitle",
"settingsViewerGestureSideTapNext", "settingsViewerGestureSideTapNext",
"settingsViewerUseCutout", "settingsViewerUseCutout",
@ -1001,6 +1003,8 @@
"settingsCollectionQuickActionTabSelecting", "settingsCollectionQuickActionTabSelecting",
"settingsCollectionBrowsingQuickActionEditorBanner", "settingsCollectionBrowsingQuickActionEditorBanner",
"settingsCollectionSelectionQuickActionEditorBanner", "settingsCollectionSelectionQuickActionEditorBanner",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerSectionTitle", "settingsViewerSectionTitle",
"settingsViewerGestureSideTapNext", "settingsViewerGestureSideTapNext",
"settingsViewerUseCutout", "settingsViewerUseCutout",
@ -1176,6 +1180,8 @@
"cs": [ "cs": [
"settingsVideoEnablePip", "settingsVideoEnablePip",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle" "settingsVideoBackgroundModeDialogTitle"
], ],
@ -1211,6 +1217,8 @@
"placePageTitle", "placePageTitle",
"placeEmpty", "placeEmpty",
"settingsConfirmationVaultDataLoss", "settingsConfirmationVaultDataLoss",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle", "settingsVideoBackgroundModeDialogTitle",
"settingsDisablingBinWarningDialogMessage" "settingsDisablingBinWarningDialogMessage"
@ -1226,10 +1234,22 @@
"drawerPlacePage", "drawerPlacePage",
"placePageTitle", "placePageTitle",
"placeEmpty", "placeEmpty",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle" "settingsVideoBackgroundModeDialogTitle"
], ],
"es": [
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone"
],
"eu": [
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone"
],
"fa": [ "fa": [
"clearTooltip", "clearTooltip",
"chipActionGoToPlacePage", "chipActionGoToPlacePage",
@ -1533,6 +1553,8 @@
"settingsCollectionQuickActionTabSelecting", "settingsCollectionQuickActionTabSelecting",
"settingsCollectionBrowsingQuickActionEditorBanner", "settingsCollectionBrowsingQuickActionEditorBanner",
"settingsCollectionSelectionQuickActionEditorBanner", "settingsCollectionSelectionQuickActionEditorBanner",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerSectionTitle", "settingsViewerSectionTitle",
"settingsViewerGestureSideTapNext", "settingsViewerGestureSideTapNext",
"settingsViewerUseCutout", "settingsViewerUseCutout",
@ -1704,6 +1726,11 @@
"filePickerUseThisFolder" "filePickerUseThisFolder"
], ],
"fr": [
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone"
],
"gl": [ "gl": [
"columnCount", "columnCount",
"chipActionGoToPlacePage", "chipActionGoToPlacePage",
@ -2037,6 +2064,8 @@
"settingsCollectionQuickActionTabSelecting", "settingsCollectionQuickActionTabSelecting",
"settingsCollectionBrowsingQuickActionEditorBanner", "settingsCollectionBrowsingQuickActionEditorBanner",
"settingsCollectionSelectionQuickActionEditorBanner", "settingsCollectionSelectionQuickActionEditorBanner",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerSectionTitle", "settingsViewerSectionTitle",
"settingsViewerGestureSideTapNext", "settingsViewerGestureSideTapNext",
"settingsViewerUseCutout", "settingsViewerUseCutout",
@ -2672,6 +2701,8 @@
"settingsCollectionQuickActionTabSelecting", "settingsCollectionQuickActionTabSelecting",
"settingsCollectionBrowsingQuickActionEditorBanner", "settingsCollectionBrowsingQuickActionEditorBanner",
"settingsCollectionSelectionQuickActionEditorBanner", "settingsCollectionSelectionQuickActionEditorBanner",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerSectionTitle", "settingsViewerSectionTitle",
"settingsViewerGestureSideTapNext", "settingsViewerGestureSideTapNext",
"settingsViewerUseCutout", "settingsViewerUseCutout",
@ -2845,6 +2876,11 @@
"filePickerUseThisFolder" "filePickerUseThisFolder"
], ],
"id": [
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone"
],
"it": [ "it": [
"chipActionGoToPlacePage", "chipActionGoToPlacePage",
"lengthUnitPixel", "lengthUnitPixel",
@ -2857,6 +2893,8 @@
"drawerPlacePage", "drawerPlacePage",
"placePageTitle", "placePageTitle",
"placeEmpty", "placeEmpty",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle" "settingsVideoBackgroundModeDialogTitle"
], ],
@ -2904,6 +2942,8 @@
"placeEmpty", "placeEmpty",
"settingsModificationWarningDialogMessage", "settingsModificationWarningDialogMessage",
"settingsConfirmationVaultDataLoss", "settingsConfirmationVaultDataLoss",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle", "settingsVideoBackgroundModeDialogTitle",
@ -2914,6 +2954,11 @@
"settingsWidgetDisplayedItem" "settingsWidgetDisplayedItem"
], ],
"ko": [
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone"
],
"lt": [ "lt": [
"columnCount", "columnCount",
"chipActionGoToPlacePage", "chipActionGoToPlacePage",
@ -2951,6 +2996,8 @@
"placeEmpty", "placeEmpty",
"settingsModificationWarningDialogMessage", "settingsModificationWarningDialogMessage",
"settingsConfirmationVaultDataLoss", "settingsConfirmationVaultDataLoss",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle", "settingsVideoBackgroundModeDialogTitle",
@ -2965,6 +3012,8 @@
"settingsVideoEnablePip", "settingsVideoEnablePip",
"patternDialogEnter", "patternDialogEnter",
"patternDialogConfirm", "patternDialogConfirm",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle" "settingsVideoBackgroundModeDialogTitle"
], ],
@ -3017,6 +3066,8 @@
"placeEmpty", "placeEmpty",
"settingsModificationWarningDialogMessage", "settingsModificationWarningDialogMessage",
"settingsConfirmationVaultDataLoss", "settingsConfirmationVaultDataLoss",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerShowRatingTags", "settingsViewerShowRatingTags",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
@ -3241,6 +3292,8 @@
"settingsCollectionQuickActionTabSelecting", "settingsCollectionQuickActionTabSelecting",
"settingsCollectionBrowsingQuickActionEditorBanner", "settingsCollectionBrowsingQuickActionEditorBanner",
"settingsCollectionSelectionQuickActionEditorBanner", "settingsCollectionSelectionQuickActionEditorBanner",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerSectionTitle", "settingsViewerSectionTitle",
"settingsViewerGestureSideTapNext", "settingsViewerGestureSideTapNext",
"settingsViewerUseCutout", "settingsViewerUseCutout",
@ -3350,7 +3403,14 @@
"wallpaperUseScrollEffect" "wallpaperUseScrollEffect"
], ],
"pl": [
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone"
],
"pt": [ "pt": [
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundModeDialogTitle" "settingsVideoBackgroundModeDialogTitle"
], ],
@ -3366,6 +3426,8 @@
"drawerPlacePage", "drawerPlacePage",
"placePageTitle", "placePageTitle",
"placeEmpty", "placeEmpty",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle" "settingsVideoBackgroundModeDialogTitle"
], ],
@ -3383,6 +3445,8 @@
"drawerPlacePage", "drawerPlacePage",
"placeEmpty", "placeEmpty",
"settingsConfirmationVaultDataLoss", "settingsConfirmationVaultDataLoss",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle", "settingsVideoBackgroundModeDialogTitle",
"settingsVideoGestureVerticalDragBrightnessVolume" "settingsVideoGestureVerticalDragBrightnessVolume"
@ -3638,6 +3702,8 @@
"settingsCollectionQuickActionTabSelecting", "settingsCollectionQuickActionTabSelecting",
"settingsCollectionBrowsingQuickActionEditorBanner", "settingsCollectionBrowsingQuickActionEditorBanner",
"settingsCollectionSelectionQuickActionEditorBanner", "settingsCollectionSelectionQuickActionEditorBanner",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerSectionTitle", "settingsViewerSectionTitle",
"settingsViewerGestureSideTapNext", "settingsViewerGestureSideTapNext",
"settingsViewerUseCutout", "settingsViewerUseCutout",
@ -3996,6 +4062,8 @@
"settingsCollectionQuickActionTabSelecting", "settingsCollectionQuickActionTabSelecting",
"settingsCollectionBrowsingQuickActionEditorBanner", "settingsCollectionBrowsingQuickActionEditorBanner",
"settingsCollectionSelectionQuickActionEditorBanner", "settingsCollectionSelectionQuickActionEditorBanner",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerSectionTitle", "settingsViewerSectionTitle",
"settingsViewerGestureSideTapNext", "settingsViewerGestureSideTapNext",
"settingsViewerUseCutout", "settingsViewerUseCutout",
@ -4200,11 +4268,18 @@
"placePageTitle", "placePageTitle",
"placeEmpty", "placeEmpty",
"settingsConfirmationVaultDataLoss", "settingsConfirmationVaultDataLoss",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle", "settingsVideoBackgroundModeDialogTitle",
"settingsDisablingBinWarningDialogMessage" "settingsDisablingBinWarningDialogMessage"
], ],
"uk": [
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone"
],
"zh": [ "zh": [
"chipActionGoToPlacePage", "chipActionGoToPlacePage",
"chipActionLock", "chipActionLock",
@ -4240,6 +4315,8 @@
"placeEmpty", "placeEmpty",
"settingsModificationWarningDialogMessage", "settingsModificationWarningDialogMessage",
"settingsConfirmationVaultDataLoss", "settingsConfirmationVaultDataLoss",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle", "settingsVideoBackgroundModeDialogTitle",
@ -4285,6 +4362,8 @@
"placeEmpty", "placeEmpty",
"settingsModificationWarningDialogMessage", "settingsModificationWarningDialogMessage",
"settingsConfirmationVaultDataLoss", "settingsConfirmationVaultDataLoss",
"settingsCollectionBurstPatternsTile",
"settingsCollectionBurstPatternsNone",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsVideoBackgroundMode", "settingsVideoBackgroundMode",
"settingsVideoBackgroundModeDialogTitle", "settingsVideoBackgroundModeDialogTitle",