diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b62b5c06..b7aaca3ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### Added - Viewer: Info page editing actions available as quick actions +- Video: subtitle vertical position option - Info: export metadata to text file - Accessibility: apply bold font system setting - `libre` app flavor (no mobile service maps, no Crashlytics) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4a7d61d98..93e32558f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -198,6 +198,9 @@ "displayRefreshRatePreferHighest": "Highest rate", "displayRefreshRatePreferLowest": "Lowest rate", + "subtitlePositionTop": "Top", + "subtitlePositionBottom": "Bottom", + "videoPlaybackSkip": "Skip", "videoPlaybackMuted": "Play muted", "videoPlaybackWithSound": "Play with sound", @@ -738,6 +741,8 @@ "settingsSubtitleThemeSample": "This is a sample.", "settingsSubtitleThemeTextAlignmentTile": "Text alignment", "settingsSubtitleThemeTextAlignmentDialogTitle": "Text Alignment", + "settingsSubtitleThemeTextPositionTile": "Text position", + "settingsSubtitleThemeTextPositionDialogTitle": "Text Position", "settingsSubtitleThemeTextSize": "Text size", "settingsSubtitleThemeShowOutline": "Show outline and shadow", "settingsSubtitleThemeTextColor": "Text color", diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 588336d0e..ea662d7be 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -98,6 +98,7 @@ class SettingsDefaults { // subtitles static const subtitleFontSize = 20.0; static const subtitleTextAlignment = TextAlign.center; + static const subtitleTextPosition = SubtitlePosition.bottom; static const subtitleShowOutline = true; static const subtitleTextColor = Colors.white; static const subtitleBackgroundColor = Colors.transparent; diff --git a/lib/model/settings/enums/enums.dart b/lib/model/settings/enums/enums.dart index cf3f74edb..35a74b2da 100644 --- a/lib/model/settings/enums/enums.dart +++ b/lib/model/settings/enums/enums.dart @@ -20,6 +20,8 @@ enum KeepScreenOn { never, viewerOnly, always } enum SlideshowVideoPlayback { skip, playMuted, playWithSound } +enum SubtitlePosition { top, bottom } + enum UnitSystem { metric, imperial } enum VideoControls { play, playSeek, playOutside, none } diff --git a/lib/model/settings/enums/subtitle_position.dart b/lib/model/settings/enums/subtitle_position.dart new file mode 100644 index 000000000..88d49d6f1 --- /dev/null +++ b/lib/model/settings/enums/subtitle_position.dart @@ -0,0 +1,24 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +import 'enums.dart'; + +extension ExtraSubtitlePosition on SubtitlePosition { + String getName(BuildContext context) { + switch (this) { + case SubtitlePosition.top: + return context.l10n.subtitlePositionTop; + case SubtitlePosition.bottom: + return context.l10n.subtitlePositionBottom; + } + } + + TextAlignVertical toTextAlignVertical() { + switch (this) { + case SubtitlePosition.top: + return TextAlignVertical.top; + case SubtitlePosition.bottom: + return TextAlignVertical.bottom; + } + } +} diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 40431a1a5..91d98e7ee 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -123,6 +123,7 @@ class Settings extends ChangeNotifier { // subtitles static const subtitleFontSizeKey = 'subtitle_font_size'; static const subtitleTextAlignmentKey = 'subtitle_text_alignment'; + static const subtitleTextPositionKey = 'subtitle_text_position'; static const subtitleShowOutlineKey = 'subtitle_show_outline'; static const subtitleTextColorKey = 'subtitle_text_color'; static const subtitleBackgroundColorKey = 'subtitle_background_color'; @@ -573,6 +574,10 @@ class Settings extends ChangeNotifier { set subtitleTextAlignment(TextAlign newValue) => setAndNotify(subtitleTextAlignmentKey, newValue.toString()); + SubtitlePosition get subtitleTextPosition => getEnumOrDefault(subtitleTextPositionKey, SettingsDefaults.subtitleTextPosition, SubtitlePosition.values); + + set subtitleTextPosition(SubtitlePosition newValue) => setAndNotify(subtitleTextPositionKey, newValue.toString()); + bool get subtitleShowOutline => getBool(subtitleShowOutlineKey) ?? SettingsDefaults.subtitleShowOutline; set subtitleShowOutline(bool newValue) => setAndNotify(subtitleShowOutlineKey, newValue); @@ -963,6 +968,7 @@ class Settings extends ChangeNotifier { case videoLoopModeKey: case videoControlsKey: case subtitleTextAlignmentKey: + case subtitleTextPositionKey: case mapStyleKey: case mapDefaultCenterKey: case coordinateFormatKey: diff --git a/lib/widgets/settings/video/subtitle_sample.dart b/lib/widgets/settings/video/subtitle_sample.dart index 16399c7b5..1a2af438f 100644 --- a/lib/widgets/settings/video/subtitle_sample.dart +++ b/lib/widgets/settings/video/subtitle_sample.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/basic/outlined_text.dart'; @@ -20,6 +21,7 @@ class SubtitleSample extends StatelessWidget { return Consumer( builder: (context, settings, child) { final textAlign = settings.subtitleTextAlignment; + final textPosition = settings.subtitleTextPosition; final outlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity); final shadows = [ Shadow( @@ -40,7 +42,7 @@ class SubtitleSample extends StatelessWidget { ), height: 128, child: AnimatedAlign( - alignment: _getAlignment(textAlign), + alignment: _getAlignment(textAlign, textPosition), curve: Curves.easeInOutCubic, duration: const Duration(milliseconds: 400), child: Padding( @@ -75,15 +77,28 @@ class SubtitleSample extends StatelessWidget { ); } - Alignment _getAlignment(TextAlign textAlign) { - switch (textAlign) { - case TextAlign.left: - return Alignment.bottomLeft; - case TextAlign.right: - return Alignment.bottomRight; - case TextAlign.center: - default: - return Alignment.bottomCenter; + Alignment _getAlignment(TextAlign textAlign, SubtitlePosition textPosition) { + switch (textPosition) { + case SubtitlePosition.top: + switch (textAlign) { + case TextAlign.left: + return Alignment.topLeft; + case TextAlign.right: + return Alignment.topRight; + case TextAlign.center: + default: + return Alignment.topCenter; + } + case SubtitlePosition.bottom: + switch (textAlign) { + case TextAlign.left: + return Alignment.bottomLeft; + case TextAlign.right: + return Alignment.bottomRight; + case TextAlign.center: + default: + return Alignment.bottomCenter; + } } } } diff --git a/lib/widgets/settings/video/subtitle_theme.dart b/lib/widgets/settings/video/subtitle_theme.dart index c60ab865f..66c597032 100644 --- a/lib/widgets/settings/video/subtitle_theme.dart +++ b/lib/widgets/settings/video/subtitle_theme.dart @@ -1,3 +1,5 @@ +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/enums/subtitle_position.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/color_list_tile.dart'; import 'package:aves/widgets/common/basic/slider_list_tile.dart'; @@ -40,6 +42,14 @@ class SubtitleThemePage extends StatelessWidget { tileTitle: context.l10n.settingsSubtitleThemeTextAlignmentTile, dialogTitle: context.l10n.settingsSubtitleThemeTextAlignmentDialogTitle, ), + SettingsSelectionListTile( + values: const [SubtitlePosition.top, SubtitlePosition.bottom], + getName: (context, v) => v.getName(context), + selector: (context, s) => s.subtitleTextPosition, + onSelection: (v) => settings.subtitleTextPosition = v, + tileTitle: context.l10n.settingsSubtitleThemeTextPositionTile, + dialogTitle: context.l10n.settingsSubtitleThemeTextPositionDialogTitle, + ), SliderListTile( title: context.l10n.settingsSubtitleThemeTextSize, value: settings.subtitleFontSize, diff --git a/lib/widgets/viewer/visual/subtitle/subtitle.dart b/lib/widgets/viewer/visual/subtitle/subtitle.dart index a90bf5424..16a1a60d3 100644 --- a/lib/widgets/viewer/visual/subtitle/subtitle.dart +++ b/lib/widgets/viewer/visual/subtitle/subtitle.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/settings/enums/subtitle_position.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/outlined_text.dart'; import 'package:aves/widgets/common/basic/text_background_painter.dart'; @@ -33,6 +34,7 @@ class VideoSubtitles extends StatelessWidget { child: Consumer( builder: (context, settings, child) { final baseTextAlign = settings.subtitleTextAlignment; + final baseTextAlignY = settings.subtitleTextPosition.toTextAlignVertical(); final baseOutlineWidth = settings.subtitleShowOutline ? 1 : 0; final baseOutlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity); final baseShadows = [ @@ -119,7 +121,8 @@ class VideoSubtitles extends StatelessWidget { ); }).toList(); final drawingPaths = extraStyle.drawingPaths; - final textAlign = extraStyle.hAlign ?? (position != null ? TextAlign.center : baseTextAlign); + final textHAlign = extraStyle.hAlign ?? (position != null ? TextAlign.center : baseTextAlign); + final textVAlign = extraStyle.vAlign ?? (position != null ? TextAlignVertical.bottom : baseTextAlignY); Widget child; if (drawingPaths != null) { @@ -138,7 +141,7 @@ class VideoSubtitles extends StatelessWidget { outlineWidth: outlineWidth * (position != null ? viewScale : baseOutlineWidth), outlineColor: extraStyle.borderColor ?? baseOutlineColor, outlineBlurSigma: extraStyle.edgeBlur ?? 0, - textAlign: textAlign, + textAlign: textHAlign, ); } @@ -154,7 +157,7 @@ class VideoSubtitles extends StatelessWidget { final textHeight = para.getMaxIntrinsicHeight(double.infinity); late double anchorOffsetX, anchorOffsetY; - switch (textAlign) { + switch (textHAlign) { case TextAlign.left: anchorOffsetX = 0; break; @@ -166,7 +169,7 @@ class VideoSubtitles extends StatelessWidget { anchorOffsetX = -textWidth / 2; break; } - switch (extraStyle.vAlign ?? TextAlignVertical.bottom) { + switch (textVAlign) { case TextAlignVertical.top: anchorOffsetY = 0; break; @@ -214,7 +217,7 @@ class VideoSubtitles extends StatelessWidget { if (position == null) { late double alignX; - switch (textAlign) { + switch (textHAlign) { case TextAlign.left: alignX = -1; break; @@ -227,7 +230,7 @@ class VideoSubtitles extends StatelessWidget { break; } late double alignY; - switch (extraStyle.vAlign) { + switch (textVAlign) { case TextAlignVertical.top: alignY = -bottom; break; @@ -248,7 +251,7 @@ class VideoSubtitles extends StatelessWidget { style: DefaultTextStyle.of(context).style.merge(spans.first.style!.copyWith( backgroundColor: settings.subtitleBackgroundColor, )), - textAlign: textAlign, + textAlign: textHAlign, child: child, ), ), diff --git a/untranslated.json b/untranslated.json index eddc579da..da73abc50 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1,15 +1,27 @@ { "de": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "el": [ "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", "tagEditorSectionPlaceholders" ], "es": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "fa": [ @@ -151,6 +163,8 @@ "accessibilityAnimationsKeep", "displayRefreshRatePreferHighest", "displayRefreshRatePreferLowest", + "subtitlePositionTop", + "subtitlePositionBottom", "videoPlaybackSkip", "videoPlaybackMuted", "videoPlaybackWithSound", @@ -476,6 +490,8 @@ "settingsSubtitleThemeSample", "settingsSubtitleThemeTextAlignmentTile", "settingsSubtitleThemeTextAlignmentDialogTitle", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", "settingsSubtitleThemeTextSize", "settingsSubtitleThemeShowOutline", "settingsSubtitleThemeTextColor", @@ -596,7 +612,11 @@ ], "fr": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "gl": [ @@ -605,6 +625,8 @@ "accessibilityAnimationsKeep", "displayRefreshRatePreferHighest", "displayRefreshRatePreferLowest", + "subtitlePositionTop", + "subtitlePositionBottom", "videoPlaybackSkip", "videoPlaybackMuted", "videoPlaybackWithSound", @@ -930,6 +952,8 @@ "settingsSubtitleThemeSample", "settingsSubtitleThemeTextAlignmentTile", "settingsSubtitleThemeTextAlignmentDialogTitle", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", "settingsSubtitleThemeTextSize", "settingsSubtitleThemeShowOutline", "settingsSubtitleThemeTextColor", @@ -1050,20 +1074,36 @@ ], "id": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "it": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "ja": [ "chipActionFilterIn", - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "ko": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "nb": [ @@ -1077,6 +1117,8 @@ "mapStyleStamenToner", "mapStyleStamenWatercolor", "keepScreenOnViewerOnly", + "subtitlePositionTop", + "subtitlePositionBottom", "viewerTransitionFade", "albumTierSpecial", "storageAccessDialogMessage", @@ -1154,6 +1196,8 @@ "settingsViewerEnableOverlayBlurEffect", "settingsSlideshowShuffle", "settingsSubtitleThemeSample", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", "settingsAllowInstalledAppAccess", "settingsAllowMediaManagement", "settingsHiddenFiltersBanner", @@ -1181,7 +1225,11 @@ ], "nl": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "pl": [ @@ -1230,6 +1278,8 @@ "accessibilityAnimationsKeep", "displayRefreshRatePreferHighest", "displayRefreshRatePreferLowest", + "subtitlePositionTop", + "subtitlePositionBottom", "videoPlaybackSkip", "videoPlaybackMuted", "videoPlaybackWithSound", @@ -1555,6 +1605,8 @@ "settingsSubtitleThemeSample", "settingsSubtitleThemeTextAlignmentTile", "settingsSubtitleThemeTextAlignmentDialogTitle", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", "settingsSubtitleThemeTextSize", "settingsSubtitleThemeShowOutline", "settingsSubtitleThemeTextColor", @@ -1675,20 +1727,36 @@ ], "pt": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "ru": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "tr": [ - "entryInfoActionExportMetadata" + "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle" ], "zh": [ "entryInfoActionExportMetadata", + "subtitlePositionTop", + "subtitlePositionBottom", "editEntryLocationDialogSetCustom", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", "settingsAllowMediaManagement", "tagEditorSectionPlaceholders", "tagPlaceholderCountry",