settings: subtitle theme
This commit is contained in:
parent
4345f46cc2
commit
caedb78f4f
13 changed files with 560 additions and 118 deletions
|
@ -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",
|
||||
|
|
|
@ -282,6 +282,21 @@
|
|||
"settingsVideoQuickActionsTile": "빠른 동영상 작업",
|
||||
"settingsVideoQuickActionEditorTitle": "빠른 동영상 작업",
|
||||
|
||||
"settingsSubtitleThemeTile": "자막",
|
||||
"settingsSubtitleThemeTitle": "자막",
|
||||
"settingsSubtitleThemeSample": "샘플입니다.",
|
||||
"settingsSubtitleThemeTextAlignmentTile": "정렬",
|
||||
"settingsSubtitleThemeTextAlignmentTitle": "정렬",
|
||||
"settingsSubtitleThemeTextSize": "글자 크기",
|
||||
"settingsSubtitleThemeShowOutline": "윤곽 및 그림자 표시",
|
||||
"settingsSubtitleThemeTextColor": "글자 색상",
|
||||
"settingsSubtitleThemeTextOpacity": "글자 투명도",
|
||||
"settingsSubtitleThemeBackgroundColor": "배경 색상",
|
||||
"settingsSubtitleThemeBackgroundOpacity": "배경 투명도",
|
||||
"settingsSubtitleThemeTextAlignmentLeft": "왼쪽",
|
||||
"settingsSubtitleThemeTextAlignmentCenter": "가운데",
|
||||
"settingsSubtitleThemeTextAlignmentRight": "오른쪽",
|
||||
|
||||
"settingsSectionPrivacy": "개인정보 보호",
|
||||
"settingsEnableAnalytics": "진단 데이터 보내기",
|
||||
"settingsSaveSearchHistory": "검색기록",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -142,6 +142,99 @@ class Constants {
|
|||
),
|
||||
];
|
||||
|
||||
static const List<Dependency> 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<Dependency> dartPackages = [
|
||||
Dependency(
|
||||
name: 'Collection',
|
||||
|
@ -216,93 +309,6 @@ class Constants {
|
|||
sourceUrl: 'https://github.com/renggli/dart-xml',
|
||||
),
|
||||
];
|
||||
|
||||
static const List<Dependency> 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 {
|
||||
|
|
100
lib/widgets/common/basic/color_list_tile.dart
Normal file
100
lib/widgets/common/basic/color_list_tile.dart
Normal file
|
@ -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<Color> 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<Color>(
|
||||
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<ColorPickerDialog> {
|
||||
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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
59
lib/widgets/common/basic/slider_list_tile.dart
Normal file
59
lib/widgets/common/basic/slider_list_tile.dart
Normal file
|
@ -0,0 +1,59 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class SliderListTile extends StatelessWidget {
|
||||
final String title;
|
||||
final double value;
|
||||
final ValueChanged<double>? 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
82
lib/widgets/settings/video/subtitle_sample.dart
Normal file
82
lib/widgets/settings/video/subtitle_sample.dart
Normal file
|
@ -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<Settings>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
126
lib/widgets/settings/video/subtitle_theme.dart
Normal file
126
lib/widgets/settings/video/subtitle_theme.dart
Normal file
|
@ -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<Settings>(
|
||||
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<TextAlign>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<TextAlign>(
|
||||
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 '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CollectionSource>().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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,13 +16,7 @@ class VideoSubtitles extends StatelessWidget {
|
|||
final ValueNotifier<ViewState> 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<MediaQueryData>().size;
|
||||
|
||||
return ValueListenableBuilder<ViewState>(
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,6 +31,7 @@ dependencies:
|
|||
firebase_core:
|
||||
firebase_analytics:
|
||||
firebase_crashlytics:
|
||||
flex_color_picker:
|
||||
flutter_highlight:
|
||||
flutter_map:
|
||||
flutter_markdown:
|
||||
|
|
Loading…
Reference in a new issue