diff --git a/CHANGELOG.md b/CHANGELOG.md index c87b5c714..2e140a4d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. - Stats: open full top listings - Slideshow: option for no transition - Widget: tap action setting +- Wallpaper: scroll effect option ### Fixed diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7cc4d0918..b241fc639 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -861,6 +861,8 @@ "viewerInfoSearchSuggestionResolution": "Resolution", "viewerInfoSearchSuggestionRights": "Rights", + "wallpaperUseScrollEffect": "Use scroll effect on home screen", + "tagEditorPageTitle": "Edit Tags", "tagEditorPageNewTagFieldLabel": "New tag", "tagEditorPageAddTagTooltip": "Add tag", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 4f90b5d4f..9b918573c 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -676,6 +676,8 @@ "viewerInfoSearchSuggestionResolution": "Résolution", "viewerInfoSearchSuggestionRights": "Droits", + "wallpaperUseScrollEffect": "Utiliser l’effet de défilement sur l’écran d’accueil", + "tagEditorPageTitle": "Modifier les libellés", "tagEditorPageNewTagFieldLabel": "Nouveau libellé", "tagEditorPageAddTagTooltip": "Ajouter le libellé", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 2772a3673..d456f9b58 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -676,6 +676,8 @@ "viewerInfoSearchSuggestionResolution": "해상도", "viewerInfoSearchSuggestionRights": "권리", + "wallpaperUseScrollEffect": "홈 화면에 스크롤 효과 사용", + "tagEditorPageTitle": "태그 수정", "tagEditorPageNewTagFieldLabel": "새 태그", "tagEditorPageAddTagTooltip": "태그 추가", diff --git a/lib/widgets/dialogs/aves_selection_dialog.dart b/lib/widgets/dialogs/aves_selection_dialog.dart index a06c385f6..0159ed52d 100644 --- a/lib/widgets/dialogs/aves_selection_dialog.dart +++ b/lib/widgets/dialogs/aves_selection_dialog.dart @@ -67,7 +67,21 @@ class _AvesSelectionDialogState extends State> { padding: const EdgeInsets.all(16), child: Text(message), ), - ...widget.options.entries.map((kv) => _buildRadioListTile(kv.key, kv.value, needConfirmation)), + ...widget.options.entries.map((kv) { + final value = kv.key; + final title = kv.value; + return SelectionRadioListTile( + // key is expected by test driver + key: Key(value.toString()), + value: value, + title: title, + optionSubtitleBuilder: widget.optionSubtitleBuilder, + needConfirmation: needConfirmation, + dense: widget.dense, + getGroupValue: () => _selectedValue, + setGroupValue: (v) => setState(() => _selectedValue = v), + ); + }), ], actions: [ TextButton( @@ -82,17 +96,39 @@ class _AvesSelectionDialogState extends State> { ], ); } +} - Widget _buildRadioListTile(T value, String title, bool needConfirmation) { - final subtitle = widget.optionSubtitleBuilder?.call(value); +class SelectionRadioListTile extends StatelessWidget { + final T value; + final String title; + final TextBuilder? 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( // key is expected by test driver key: Key(value.toString()), value: value, - groupValue: _selectedValue, + groupValue: getGroupValue(), onChanged: (v) { if (needConfirmation) { - setState(() => _selectedValue = v as T); + setGroupValue(v as T); } else { Navigator.pop(context, v); } @@ -112,7 +148,7 @@ class _AvesSelectionDialogState extends State> { maxLines: 1, ) : null, - dense: widget.dense, + dense: dense, ); } } diff --git a/lib/widgets/dialogs/wallpaper_settings_dialog.dart b/lib/widgets/dialogs/wallpaper_settings_dialog.dart new file mode 100644 index 000000000..ba8d6ce73 --- /dev/null +++ b/lib/widgets/dialogs/wallpaper_settings_dialog.dart @@ -0,0 +1,53 @@ +import 'package:aves/model/device.dart'; +import 'package:aves/model/wallpaper_target.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:tuple/tuple.dart'; + +import 'aves_dialog.dart'; + +class WallpaperSettingsDialog extends StatefulWidget { + const WallpaperSettingsDialog({super.key}); + + @override + State createState() => _WallpaperSettingsDialogState(); +} + +class _WallpaperSettingsDialogState extends State { + WallpaperTarget _selectedTarget = WallpaperTarget.home; + bool _useScrollEffect = true; + + @override + Widget build(BuildContext context) { + return AvesDialog( + scrollableContent: [ + if (device.canSetLockScreenWallpaper) + ...WallpaperTarget.values.map((value) { + return SelectionRadioListTile( + value: value, + title: value.getName(context), + needConfirmation: true, + getGroupValue: () => _selectedTarget, + setGroupValue: (v) => setState(() => _selectedTarget = v), + ); + }), + SwitchListTile( + value: _useScrollEffect, + onChanged: (v) => setState(() => _useScrollEffect = v), + title: Text(context.l10n.wallpaperUseScrollEffect), + ) + ], + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + ), + TextButton( + onPressed: () => Navigator.pop(context, Tuple2(_selectedTarget, _useScrollEffect)), + child: Text(context.l10n.applyButtonLabel), + ), + ], + ); + } +} diff --git a/lib/widgets/viewer/overlay/wallpaper_buttons.dart b/lib/widgets/viewer/overlay/wallpaper_buttons.dart index 1e9e0022d..39dd5b9eb 100644 --- a/lib/widgets/viewer/overlay/wallpaper_buttons.dart +++ b/lib/widgets/viewer/overlay/wallpaper_buttons.dart @@ -2,14 +2,13 @@ import 'dart:async'; import 'dart:math'; import 'dart:ui' as ui; -import 'package:aves/model/device.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_images.dart'; import 'package:aves/model/wallpaper_target.dart'; import 'package:aves/services/wallpaper_service.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:aves/widgets/dialogs/wallpaper_settings_dialog.dart'; import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; @@ -18,6 +17,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; class WallpaperButtons extends StatelessWidget with FeedbackMixin { final AvesEntry entry; @@ -56,19 +56,14 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { Future _setWallpaper(BuildContext context) async { final l10n = context.l10n; - var target = WallpaperTarget.home; - if (device.canSetLockScreenWallpaper) { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: WallpaperTarget.home, - options: Map.fromEntries(WallpaperTarget.values.map((v) => MapEntry(v, v.getName(context)))), - confirmationButtonLabel: l10n.continueButtonLabel, - ), - ); - if (value == null) return; - target = value; - } + final value = await showDialog>( + context: context, + builder: (context) => const WallpaperSettingsDialog(), + ); + if (value == null) return; + + final target = value.item1; + final useScrollEffect = value.item2; final reportController = StreamController.broadcast(); unawaited(showOpReport( @@ -76,17 +71,15 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { opStream: reportController.stream, )); - final viewState = context.read().getOrCreateController(entry).value; - final viewportSize = viewState.viewportSize; - final contentSize = viewState.contentSize; - final scale = viewState.scale; - if (viewportSize == null || contentSize == null || contentSize.isEmpty || scale == null) return; + var region = _getVisibleRegion(context); + if (region == null) return; - final center = (contentSize / 2 - viewState.position / scale) as Size; - final regionSize = viewportSize / scale; - final regionTopLeft = (center - regionSize / 2) as Offset; - final region = Rect.fromLTWH(regionTopLeft.dx, regionTopLeft.dy, regionSize.width, regionSize.height); - final bytes = await _getBytes(context, scale, region); + if (useScrollEffect) { + final deltaX = min(region.left, entry.displaySize.width - region.right); + region = Rect.fromLTRB(region.left - deltaX, region.top, region.right + deltaX, region.bottom); + } + + final bytes = await _getBytes(context, region); final success = bytes != null && await WallpaperService.set(bytes, target); unawaited(reportController.close()); @@ -98,9 +91,25 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { } } - Future _getBytes(BuildContext context, double scale, Rect displayRegion) async { + Rect? _getVisibleRegion(BuildContext context) { + final viewState = context.read().getOrCreateController(entry).value; + final viewportSize = viewState.viewportSize; + final contentSize = viewState.contentSize; + final scale = viewState.scale; + if (viewportSize == null || contentSize == null || contentSize.isEmpty || scale == null) return null; + + final center = (contentSize / 2 - viewState.position / scale) as Size; + final regionSize = viewportSize / scale; + final regionTopLeft = (center - regionSize / 2) as Offset; + return Rect.fromLTWH(regionTopLeft.dx, regionTopLeft.dy, regionSize.width, regionSize.height); + } + + Future _getBytes(BuildContext context, Rect displayRegion) async { + final viewState = context.read().getOrCreateController(entry).value; + final scale = viewState.scale; + final displaySize = entry.displaySize; - if (displaySize.isEmpty) return null; + if (displaySize.isEmpty || scale == null) return null; var storageRegion = Rectangle( displayRegion.left, diff --git a/untranslated.json b/untranslated.json index 15a757309..23acffbc6 100644 --- a/untranslated.json +++ b/untranslated.json @@ -9,7 +9,8 @@ "albumGroupType", "albumMimeTypeMixed", "settingsWidgetOpenPage", - "statsTopAlbumsSectionTitle" + "statsTopAlbumsSectionTitle", + "wallpaperUseScrollEffect" ], "el": [ @@ -22,7 +23,8 @@ "albumGroupType", "albumMimeTypeMixed", "settingsWidgetOpenPage", - "statsTopAlbumsSectionTitle" + "statsTopAlbumsSectionTitle", + "wallpaperUseScrollEffect" ], "es": [ @@ -51,7 +53,8 @@ "settingsConfirmationAfterMoveToBinItems", "settingsWidgetOpenPage", "statsTopAlbumsSectionTitle", - "viewerInfoLabelDescription" + "viewerInfoLabelDescription", + "wallpaperUseScrollEffect" ], "id": [ @@ -64,7 +67,8 @@ "albumGroupType", "albumMimeTypeMixed", "settingsWidgetOpenPage", - "statsTopAlbumsSectionTitle" + "statsTopAlbumsSectionTitle", + "wallpaperUseScrollEffect" ], "it": [ @@ -77,7 +81,8 @@ "albumGroupType", "albumMimeTypeMixed", "settingsWidgetOpenPage", - "statsTopAlbumsSectionTitle" + "statsTopAlbumsSectionTitle", + "wallpaperUseScrollEffect" ], "ja": [ @@ -107,7 +112,8 @@ "settingsViewerGestureSideTapNext", "settingsWidgetOpenPage", "statsTopAlbumsSectionTitle", - "viewerInfoLabelDescription" + "viewerInfoLabelDescription", + "wallpaperUseScrollEffect" ], "nl": [ @@ -120,7 +126,8 @@ "albumGroupType", "albumMimeTypeMixed", "settingsWidgetOpenPage", - "statsTopAlbumsSectionTitle" + "statsTopAlbumsSectionTitle", + "wallpaperUseScrollEffect" ], "pt": [ @@ -133,7 +140,8 @@ "albumGroupType", "albumMimeTypeMixed", "settingsWidgetOpenPage", - "statsTopAlbumsSectionTitle" + "statsTopAlbumsSectionTitle", + "wallpaperUseScrollEffect" ], "ru": [ @@ -146,7 +154,8 @@ "albumGroupType", "albumMimeTypeMixed", "settingsWidgetOpenPage", - "statsTopAlbumsSectionTitle" + "statsTopAlbumsSectionTitle", + "wallpaperUseScrollEffect" ], "tr": [ @@ -204,7 +213,8 @@ "settingsWidgetOpenPage", "statsTopAlbumsSectionTitle", "viewerSetWallpaperButtonLabel", - "viewerInfoLabelDescription" + "viewerInfoLabelDescription", + "wallpaperUseScrollEffect" ], "zh": [ @@ -217,6 +227,7 @@ "albumGroupType", "albumMimeTypeMixed", "settingsWidgetOpenPage", - "statsTopAlbumsSectionTitle" + "statsTopAlbumsSectionTitle", + "wallpaperUseScrollEffect" ] }