diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 345224fdc..620d38876 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -607,6 +607,18 @@ "@settingsSubtitleThemeTextAlignmentTile": {}, "settingsSubtitleThemeTextAlignmentTitle": "Text Alignment", "@settingsSubtitleThemeTextAlignmentTitle": {}, + "settingsSubtitleThemeTextSize": "Text size", + "@settingsSubtitleThemeTextSize": {}, + "settingsSubtitleThemeShowOutline": "Show outline and shadow", + "@settingsSubtitleThemeShowOutline": {}, + "settingsSubtitleThemeTextColor": "Text color", + "@settingsSubtitleThemeTextColor": {}, + "settingsSubtitleThemeTextOpacity": "Text opacity", + "@settingsSubtitleThemeTextOpacity": {}, + "settingsSubtitleThemeBackgroundColor": "Background color", + "@settingsSubtitleThemeBackgroundColor": {}, + "settingsSubtitleThemeBackgroundOpacity": "Background opacity", + "@settingsSubtitleThemeBackgroundOpacity": {}, "settingsSubtitleThemeTextAlignmentLeft": "Left", "@settingsSubtitleThemeTextAlignmentLeft": {}, "settingsSubtitleThemeTextAlignmentCenter": "Center", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 26188f853..8a896f624 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -282,6 +282,21 @@ "settingsVideoQuickActionsTile": "빠른 동영상 작업", "settingsVideoQuickActionEditorTitle": "빠른 동영상 작업", + "settingsSubtitleThemeTile": "자막", + "settingsSubtitleThemeTitle": "자막", + "settingsSubtitleThemeSample": "샘플입니다.", + "settingsSubtitleThemeTextAlignmentTile": "정렬", + "settingsSubtitleThemeTextAlignmentTitle": "정렬", + "settingsSubtitleThemeTextSize": "글자 크기", + "settingsSubtitleThemeShowOutline": "윤곽 및 그림자 표시", + "settingsSubtitleThemeTextColor": "글자 색상", + "settingsSubtitleThemeTextOpacity": "글자 투명도", + "settingsSubtitleThemeBackgroundColor": "배경 색상", + "settingsSubtitleThemeBackgroundOpacity": "배경 투명도", + "settingsSubtitleThemeTextAlignmentLeft": "왼쪽", + "settingsSubtitleThemeTextAlignmentCenter": "가운데", + "settingsSubtitleThemeTextAlignmentRight": "오른쪽", + "settingsSectionPrivacy": "개인정보 보호", "settingsEnableAnalytics": "진단 데이터 보내기", "settingsSaveSearchHistory": "검색기록", diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index 357b58415..1f85b0f8c 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -52,7 +52,6 @@ class Durations { // settings animations static const quickActionListAnimation = Duration(milliseconds: 200); static const quickActionHighlightAnimation = Duration(milliseconds: 200); - static const themeChangeDuration = Duration(milliseconds: 400); // delays & refresh intervals static const opToastDisplay = Duration(seconds: 3); diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 836a68a2b..43daf682b 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -142,6 +142,99 @@ class Constants { ), ]; + static const List flutterPackages = [ + Dependency( + name: 'Charts', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/google/charts/blob/master/LICENSE', + sourceUrl: 'https://github.com/google/charts', + ), + Dependency( + name: 'Decorated Icon', + license: 'MIT', + licenseUrl: 'https://github.com/benPesso/flutter_decorated_icon/blob/master/LICENSE', + sourceUrl: 'https://github.com/benPesso/flutter_decorated_icon', + ), + Dependency( + name: 'Expansion Tile Card (Aves fork)', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/deckerst/expansion_tile_card/blob/master/LICENSE', + sourceUrl: 'https://github.com/deckerst/expansion_tile_card', + ), + Dependency( + name: 'FlexColorPicker', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/rydmike/flex_color_picker/blob/master/LICENSE', + sourceUrl: 'https://github.com/rydmike/flex_color_picker', + ), + Dependency( + name: 'Flutter Highlight', + license: 'MIT', + licenseUrl: 'https://github.com/git-touch/highlight/blob/master/LICENSE', + sourceUrl: 'https://github.com/git-touch/highlight', + ), + Dependency( + name: 'Flutter Map', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/fleaflet/flutter_map/blob/master/LICENSE', + sourceUrl: 'https://github.com/fleaflet/flutter_map', + ), + Dependency( + name: 'Flutter Markdown', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/flutter_markdown/LICENSE', + sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/flutter_markdown', + ), + Dependency( + name: 'Flutter Staggered Animations', + license: 'MIT', + licenseUrl: 'https://github.com/mobiten/flutter_staggered_animations/blob/master/LICENSE', + sourceUrl: 'https://github.com/mobiten/flutter_staggered_animations', + ), + Dependency( + name: 'Flutter SVG', + license: 'MIT', + licenseUrl: 'https://github.com/dnfield/flutter_svg/blob/master/LICENSE', + sourceUrl: 'https://github.com/dnfield/flutter_svg', + ), + Dependency( + name: 'Material Design Icons Flutter', + license: 'MIT', + licenseUrl: 'https://github.com/ziofat/material_design_icons_flutter/blob/master/LICENSE', + sourceUrl: 'https://github.com/ziofat/material_design_icons_flutter', + ), + Dependency( + name: 'Overlay Support', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/boyan01/overlay_support/blob/master/LICENSE', + sourceUrl: 'https://github.com/boyan01/overlay_support', + ), + Dependency( + name: 'Palette Generator', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/palette_generator/LICENSE', + sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/palette_generator', + ), + Dependency( + name: 'Panorama', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/zesage/panorama/blob/master/LICENSE', + sourceUrl: 'https://github.com/zesage/panorama', + ), + Dependency( + name: 'Percent Indicator', + license: 'BSD 2-Clause', + licenseUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/blob/master/LICENSE', + sourceUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/', + ), + Dependency( + name: 'Provider', + license: 'MIT', + licenseUrl: 'https://github.com/rrousselGit/provider/blob/master/LICENSE', + sourceUrl: 'https://github.com/rrousselGit/provider', + ), + ]; + static const List dartPackages = [ Dependency( name: 'Collection', @@ -216,93 +309,6 @@ class Constants { sourceUrl: 'https://github.com/renggli/dart-xml', ), ]; - - static const List flutterPackages = [ - Dependency( - name: 'Charts', - license: 'Apache 2.0', - licenseUrl: 'https://github.com/google/charts/blob/master/LICENSE', - sourceUrl: 'https://github.com/google/charts', - ), - Dependency( - name: 'Decorated Icon', - license: 'MIT', - licenseUrl: 'https://github.com/benPesso/flutter_decorated_icon/blob/master/LICENSE', - sourceUrl: 'https://github.com/benPesso/flutter_decorated_icon', - ), - Dependency( - name: 'Expansion Tile Card (Aves fork)', - license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/deckerst/expansion_tile_card/blob/master/LICENSE', - sourceUrl: 'https://github.com/deckerst/expansion_tile_card', - ), - Dependency( - name: 'Flutter Highlight', - license: 'MIT', - licenseUrl: 'https://github.com/git-touch/highlight/blob/master/LICENSE', - sourceUrl: 'https://github.com/git-touch/highlight', - ), - Dependency( - name: 'Flutter Map', - license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/fleaflet/flutter_map/blob/master/LICENSE', - sourceUrl: 'https://github.com/fleaflet/flutter_map', - ), - Dependency( - name: 'Flutter Markdown', - license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/flutter_markdown/LICENSE', - sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/flutter_markdown', - ), - Dependency( - name: 'Flutter Staggered Animations', - license: 'MIT', - licenseUrl: 'https://github.com/mobiten/flutter_staggered_animations/blob/master/LICENSE', - sourceUrl: 'https://github.com/mobiten/flutter_staggered_animations', - ), - Dependency( - name: 'Flutter SVG', - license: 'MIT', - licenseUrl: 'https://github.com/dnfield/flutter_svg/blob/master/LICENSE', - sourceUrl: 'https://github.com/dnfield/flutter_svg', - ), - Dependency( - name: 'Material Design Icons Flutter', - license: 'MIT', - licenseUrl: 'https://github.com/ziofat/material_design_icons_flutter/blob/master/LICENSE', - sourceUrl: 'https://github.com/ziofat/material_design_icons_flutter', - ), - Dependency( - name: 'Overlay Support', - license: 'Apache 2.0', - licenseUrl: 'https://github.com/boyan01/overlay_support/blob/master/LICENSE', - sourceUrl: 'https://github.com/boyan01/overlay_support', - ), - Dependency( - name: 'Palette Generator', - license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/palette_generator/LICENSE', - sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/palette_generator', - ), - Dependency( - name: 'Panorama', - license: 'Apache 2.0', - licenseUrl: 'https://github.com/zesage/panorama/blob/master/LICENSE', - sourceUrl: 'https://github.com/zesage/panorama', - ), - Dependency( - name: 'Percent Indicator', - license: 'BSD 2-Clause', - licenseUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/blob/master/LICENSE', - sourceUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/', - ), - Dependency( - name: 'Provider', - license: 'MIT', - licenseUrl: 'https://github.com/rrousselGit/provider/blob/master/LICENSE', - sourceUrl: 'https://github.com/rrousselGit/provider', - ), - ]; } class Dependency { diff --git a/lib/widgets/common/basic/color_list_tile.dart b/lib/widgets/common/basic/color_list_tile.dart new file mode 100644 index 000000000..2aee5b8dc --- /dev/null +++ b/lib/widgets/common/basic/color_list_tile.dart @@ -0,0 +1,100 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/fx/borders.dart'; +import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:flex_color_picker/flex_color_picker.dart'; +import 'package:flutter/material.dart'; + +class ColorListTile extends StatelessWidget { + final String title; + final Color value; + final ValueSetter onChanged; + + static const radius = 16.0; + + const ColorListTile({ + Key? key, + required this.title, + required this.value, + required this.onChanged, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(title), + trailing: Container( + height: radius * 2, + width: radius * 2, + decoration: BoxDecoration( + color: value, + border: AvesBorder.border, + shape: BoxShape.circle, + ), + ), + contentPadding: const EdgeInsetsDirectional.only(start: 16, end: 36 - radius), + onTap: () async { + final color = await showDialog( + context: context, + builder: (context) => ColorPickerDialog( + initialValue: value, + ), + ); + if (color != null) { + onChanged(color); + } + }, + ); + } +} + +class ColorPickerDialog extends StatefulWidget { + final Color initialValue; + + const ColorPickerDialog({ + Key? key, + required this.initialValue, + }) : super(key: key); + + @override + _ColorPickerDialogState createState() => _ColorPickerDialogState(); +} + +class _ColorPickerDialogState extends State { + late Color color; + + @override + void initState() { + super.initState(); + color = widget.initialValue; + } + + @override + Widget build(BuildContext context) { + return AvesDialog( + context: context, + scrollableContent: [ + ColorPicker( + color: color, + onColorChanged: (v) => color = v, + pickersEnabled: const { + ColorPickerType.primary: false, + ColorPickerType.accent: false, + ColorPickerType.wheel: true, + }, + hasBorder: true, + borderRadius: 20, + ) + ], + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + ), + TextButton( + onPressed: () => Navigator.pop(context, color), + child: Text(context.l10n.applyButtonLabel), + ), + ], + ); + } +} diff --git a/lib/widgets/common/basic/outlined_text.dart b/lib/widgets/common/basic/outlined_text.dart index 18f1aef28..12a4812c2 100644 --- a/lib/widgets/common/basic/outlined_text.dart +++ b/lib/widgets/common/basic/outlined_text.dart @@ -25,9 +25,17 @@ class OutlinedText extends StatelessWidget { @override Widget build(BuildContext context) { + // TODO TLAD [subtitles] fix background area for mixed alphabetic-ideographic text + // as of Flutter v2.2.2, the area computed for `backgroundColor` has inconsistent height + // in case of mixed alphabetic-ideographic text. The painted boxes depends on the script. + // Possible workarounds would be to use metrics from: + // - `TextPainter.getBoxesForSelection` + // - `Paragraph.getBoxesForRange` + // and paint the background at the bottom of the `Stack` + final hasOutline = outlineWidth > 0; return Stack( children: [ - if (outlineWidth > 0) + if (hasOutline) ImageFiltered( imageFilter: outlineBlurSigma > 0 ? ImageFilter.blur( @@ -46,7 +54,7 @@ class OutlinedText extends StatelessWidget { ), Text.rich( TextSpan( - children: textSpans, + children: hasOutline ? textSpans.map(_toFillSpan).toList() : textSpans, ), textAlign: textAlign, ), @@ -64,4 +72,12 @@ class OutlinedText extends StatelessWidget { ..color = outlineColor, ), ); + + TextSpan _toFillSpan(TextSpan span) => TextSpan( + text: span.text, + children: span.children, + style: (span.style ?? const TextStyle()).copyWith( + backgroundColor: Colors.transparent, + ), + ); } diff --git a/lib/widgets/common/basic/slider_list_tile.dart b/lib/widgets/common/basic/slider_list_tile.dart new file mode 100644 index 000000000..8dc65db1c --- /dev/null +++ b/lib/widgets/common/basic/slider_list_tile.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +class SliderListTile extends StatelessWidget { + final String title; + final double value; + final ValueChanged? onChanged; + final double min; + final double max; + final int? divisions; + + const SliderListTile({ + Key? key, + required this.title, + required this.value, + required this.onChanged, + this.min = 0.0, + this.max = 1.0, + this.divisions, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SliderTheme( + data: const SliderThemeData( + overlayShape: RoundSliderOverlayShape( + // align `Slider`s on `Switch`es by matching their overlay/reaction radius + // `kRadialReactionRadius` is used when `SwitchThemeData.splashRadius` is undefined + overlayRadius: kRadialReactionRadius, + ), + ), + child: DefaultTextStyle( + style: Theme.of(context).textTheme.subtitle1!, + child: Padding( + padding: const EdgeInsets.only(top: 16, bottom: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 16), + child: Text(title), + ), + Padding( + // match `SwitchListTile.contentPadding` + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Slider( + value: value, + onChanged: onChanged, + min: min, + max: max, + divisions: divisions, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/settings/video/subtitle_sample.dart b/lib/widgets/settings/video/subtitle_sample.dart new file mode 100644 index 000000000..40917395f --- /dev/null +++ b/lib/widgets/settings/video/subtitle_sample.dart @@ -0,0 +1,82 @@ +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/widgets/common/basic/outlined_text.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/fx/borders.dart'; +import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SubtitleSample extends StatelessWidget { + const SubtitleSample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, settings, child) { + final outlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity); + final shadows = [ + Shadow( + color: outlineColor, + offset: VideoSubtitles.baseShadowOffset, + ), + ]; + + return Container( + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.bottomLeft, + end: Alignment.topRight, + colors: [ + // Bora Bora + Color(0xff2bc0e4), + Color(0xffeaecc6), + ], + ), + border: AvesBorder.border, + borderRadius: const BorderRadius.all(Radius.circular(24)), + ), + height: 128, + child: AnimatedAlign( + alignment: _getAlignment(settings.subtitleTextAlignment), + curve: Curves.easeInOutCubic, + duration: const Duration(milliseconds: 400), + child: Padding( + padding: const EdgeInsets.all(16), + child: AnimatedDefaultTextStyle( + style: TextStyle( + color: settings.subtitleTextColor, + backgroundColor: settings.subtitleBackgroundColor, + fontSize: settings.subtitleFontSize, + shadows: settings.subtitleShowOutline ? shadows : null, + ), + textAlign: settings.subtitleTextAlignment, + duration: const Duration(milliseconds: 200), + child: OutlinedText( + textSpans: [ + TextSpan( + text: context.l10n.settingsSubtitleThemeSample, + ), + ], + outlineWidth: settings.subtitleShowOutline ? 1 : 0, + outlineColor: outlineColor, + ), + ), + ), + ), + ); + }, + ); + } + + 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; + } + } +} diff --git a/lib/widgets/settings/video/subtitle_theme.dart b/lib/widgets/settings/video/subtitle_theme.dart new file mode 100644 index 000000000..1d5a3d776 --- /dev/null +++ b/lib/widgets/settings/video/subtitle_theme.dart @@ -0,0 +1,126 @@ +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'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:aves/widgets/settings/video/subtitle_sample.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SubtitleThemeTile extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(context.l10n.settingsSubtitleThemeTile), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: SubtitleThemePage.routeName), + builder: (context) => SubtitleThemePage(), + ), + ); + }, + ); + } +} + +class SubtitleThemePage extends StatelessWidget { + static const routeName = '/settings/subtitle_theme'; + + static const textAlignOptions = [TextAlign.left, TextAlign.center, TextAlign.right]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.l10n.settingsSubtitleThemeTitle), + ), + body: SafeArea( + child: Consumer( + builder: (context, settings, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.all(16), + child: SubtitleSample(), + ), + const Divider(height: 0), + Expanded( + child: ListView( + children: [ + ListTile( + title: Text(context.l10n.settingsSubtitleThemeTextAlignmentTile), + subtitle: Text(_getTextAlignName(context, settings.subtitleTextAlignment)), + onTap: () async { + final value = await showDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: settings.subtitleTextAlignment, + options: Map.fromEntries(textAlignOptions.map((v) => MapEntry(v, _getTextAlignName(context, v)))), + title: context.l10n.settingsSubtitleThemeTextAlignmentTitle, + ), + ); + if (value != null) { + settings.subtitleTextAlignment = value; + } + }, + ), + SliderListTile( + title: context.l10n.settingsSubtitleThemeTextSize, + value: settings.subtitleFontSize, + onChanged: (v) => settings.subtitleFontSize = v, + min: 10, + max: 40, + divisions: 6, + ), + ColorListTile( + title: context.l10n.settingsSubtitleThemeTextColor, + value: settings.subtitleTextColor.withOpacity(1), + onChanged: (v) => settings.subtitleTextColor = v.withOpacity(settings.subtitleTextColor.opacity), + ), + SliderListTile( + title: context.l10n.settingsSubtitleThemeTextOpacity, + value: settings.subtitleTextColor.opacity, + onChanged: (v) => settings.subtitleTextColor = settings.subtitleTextColor.withOpacity(v), + ), + ColorListTile( + title: context.l10n.settingsSubtitleThemeBackgroundColor, + value: settings.subtitleBackgroundColor.withOpacity(1), + onChanged: (v) => settings.subtitleBackgroundColor = v.withOpacity(settings.subtitleBackgroundColor.opacity), + ), + SliderListTile( + title: context.l10n.settingsSubtitleThemeBackgroundOpacity, + value: settings.subtitleBackgroundColor.opacity, + onChanged: (v) => settings.subtitleBackgroundColor = settings.subtitleBackgroundColor.withOpacity(v), + ), + SwitchListTile( + value: settings.subtitleShowOutline, + onChanged: (v) => settings.subtitleShowOutline = v, + title: Text(context.l10n.settingsSubtitleThemeShowOutline), + ), + ], + ), + ), + ], + ); + }, + ), + ), + ); + } + + String _getTextAlignName(BuildContext context, TextAlign align) { + switch (align) { + case TextAlign.left: + return context.l10n.settingsSubtitleThemeTextAlignmentLeft; + case TextAlign.center: + return context.l10n.settingsSubtitleThemeTextAlignmentCenter; + case TextAlign.right: + return context.l10n.settingsSubtitleThemeTextAlignmentRight; + default: + return ''; + } + } +} diff --git a/lib/widgets/settings/video/video.dart b/lib/widgets/settings/video/video.dart index 86b237527..e33e3cd35 100644 --- a/lib/widgets/settings/video/video.dart +++ b/lib/widgets/settings/video/video.dart @@ -9,6 +9,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart'; +import 'package:aves/widgets/settings/video/subtitle_theme.dart'; import 'package:aves/widgets/settings/video/video_actions_editor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -42,6 +43,7 @@ class VideoSection extends StatelessWidget { onChanged: (v) => context.read().changeFilterVisibility(MimeFilter.video, v), title: Text(context.l10n.settingsVideoShowVideos), ), + VideoActionsTile(), SwitchListTile( value: currentEnableVideoHardwareAcceleration, onChanged: (v) => settings.enableVideoHardwareAcceleration = v, @@ -69,7 +71,7 @@ class VideoSection extends StatelessWidget { } }, ), - VideoActionsTile(), + SubtitleThemeTile(), ], ); } diff --git a/lib/widgets/viewer/visual/subtitle/subtitle.dart b/lib/widgets/viewer/visual/subtitle/subtitle.dart index ee6c74d99..3c215c253 100644 --- a/lib/widgets/viewer/visual/subtitle/subtitle.dart +++ b/lib/widgets/viewer/visual/subtitle/subtitle.dart @@ -16,13 +16,7 @@ class VideoSubtitles extends StatelessWidget { final ValueNotifier viewStateNotifier; final bool debugMode; - static const baseOutlineColor = Colors.black; - static const baseShadows = [ - Shadow( - color: Colors.black54, - offset: Offset(1, 1), - ), - ]; + static const baseShadowOffset = Offset(1, 1); const VideoSubtitles({ Key? key, @@ -39,6 +33,13 @@ class VideoSubtitles extends StatelessWidget { builder: (context, settings, child) { final baseTextAlign = settings.subtitleTextAlignment; final baseOutlineWidth = settings.subtitleShowOutline ? 1 : 0; + final baseOutlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity); + final baseShadows = [ + Shadow( + color: baseOutlineColor, + offset: baseShadowOffset, + ), + ]; final baseStyle = TextStyle( color: settings.subtitleTextColor, backgroundColor: settings.subtitleBackgroundColor, @@ -50,18 +51,6 @@ class VideoSubtitles extends StatelessWidget { selector: (c, mq) => mq.orientation, builder: (c, orientation, child) { final bottom = orientation == Orientation.portrait ? .5 : .8; - Alignment toVerticalAlignment(SubtitleStyle extraStyle) { - switch (extraStyle.vAlign) { - case TextAlignVertical.top: - return Alignment(0, -bottom); - case TextAlignVertical.center: - return Alignment.center; - case TextAlignVertical.bottom: - default: - return Alignment(0, bottom); - } - } - final viewportSize = context.read().size; return ValueListenableBuilder( @@ -132,6 +121,7 @@ class VideoSubtitles extends StatelessWidget { ); }).toList(); final drawingPaths = extraStyle.drawingPaths; + final textAlign = extraStyle.hAlign ?? (position != null ? TextAlign.center : baseTextAlign); Widget child; if (drawingPaths != null) { @@ -150,7 +140,7 @@ class VideoSubtitles extends StatelessWidget { outlineWidth: outlineWidth * (position != null ? viewScale : baseOutlineWidth), outlineColor: extraStyle.borderColor ?? baseOutlineColor, outlineBlurSigma: extraStyle.edgeBlur ?? 0, - textAlign: extraStyle.hAlign ?? baseTextAlign, + textAlign: textAlign, ); } @@ -166,7 +156,7 @@ class VideoSubtitles extends StatelessWidget { final textHeight = para.getMaxIntrinsicHeight(double.infinity); late double anchorOffsetX, anchorOffsetY; - switch (extraStyle.hAlign ?? baseTextAlign) { + switch (textAlign) { case TextAlign.left: anchorOffsetX = 0; break; @@ -174,9 +164,7 @@ class VideoSubtitles extends StatelessWidget { anchorOffsetX = -textWidth; break; case TextAlign.center: - case TextAlign.start: - case TextAlign.end: - case TextAlign.justify: + default: anchorOffsetX = -textWidth / 2; break; } @@ -227,9 +215,38 @@ class VideoSubtitles extends StatelessWidget { } if (position == null) { - child = Align( - alignment: toVerticalAlignment(extraStyle), - child: child, + late double alignX; + switch (textAlign) { + case TextAlign.left: + alignX = -1; + break; + case TextAlign.right: + alignX = 1; + break; + case TextAlign.center: + default: + alignX = 0; + break; + } + late double alignY; + switch (extraStyle.vAlign) { + case TextAlignVertical.top: + alignY = -bottom; + break; + case TextAlignVertical.center: + alignY = 0; + break; + case TextAlignVertical.bottom: + default: + alignY = bottom; + break; + } + child = Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Align( + alignment: Alignment(alignX, alignY), + child: child, + ), ); } diff --git a/pubspec.lock b/pubspec.lock index e0dc771ca..901ad32fc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -292,6 +292,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.6" + flex_color_picker: + dependency: "direct main" + description: + name: flex_color_picker + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 3a139cea4..9bfe63a28 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: firebase_core: firebase_analytics: firebase_crashlytics: + flex_color_picker: flutter_highlight: flutter_map: flutter_markdown: