#380 subtitle vertical position option
This commit is contained in:
parent
f3bee6ec7e
commit
cbfbc436ed
10 changed files with 163 additions and 28 deletions
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
|
|
24
lib/model/settings/enums/subtitle_position.dart
Normal file
24
lib/model/settings/enums/subtitle_position.dart
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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<Settings>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SubtitlePosition>(
|
||||
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,
|
||||
|
|
|
@ -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<Settings>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue