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": {},
|
"@settingsSubtitleThemeTextAlignmentTile": {},
|
||||||
"settingsSubtitleThemeTextAlignmentTitle": "Text Alignment",
|
"settingsSubtitleThemeTextAlignmentTitle": "Text Alignment",
|
||||||
"@settingsSubtitleThemeTextAlignmentTitle": {},
|
"@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": "Left",
|
||||||
"@settingsSubtitleThemeTextAlignmentLeft": {},
|
"@settingsSubtitleThemeTextAlignmentLeft": {},
|
||||||
"settingsSubtitleThemeTextAlignmentCenter": "Center",
|
"settingsSubtitleThemeTextAlignmentCenter": "Center",
|
||||||
|
|
|
@ -282,6 +282,21 @@
|
||||||
"settingsVideoQuickActionsTile": "빠른 동영상 작업",
|
"settingsVideoQuickActionsTile": "빠른 동영상 작업",
|
||||||
"settingsVideoQuickActionEditorTitle": "빠른 동영상 작업",
|
"settingsVideoQuickActionEditorTitle": "빠른 동영상 작업",
|
||||||
|
|
||||||
|
"settingsSubtitleThemeTile": "자막",
|
||||||
|
"settingsSubtitleThemeTitle": "자막",
|
||||||
|
"settingsSubtitleThemeSample": "샘플입니다.",
|
||||||
|
"settingsSubtitleThemeTextAlignmentTile": "정렬",
|
||||||
|
"settingsSubtitleThemeTextAlignmentTitle": "정렬",
|
||||||
|
"settingsSubtitleThemeTextSize": "글자 크기",
|
||||||
|
"settingsSubtitleThemeShowOutline": "윤곽 및 그림자 표시",
|
||||||
|
"settingsSubtitleThemeTextColor": "글자 색상",
|
||||||
|
"settingsSubtitleThemeTextOpacity": "글자 투명도",
|
||||||
|
"settingsSubtitleThemeBackgroundColor": "배경 색상",
|
||||||
|
"settingsSubtitleThemeBackgroundOpacity": "배경 투명도",
|
||||||
|
"settingsSubtitleThemeTextAlignmentLeft": "왼쪽",
|
||||||
|
"settingsSubtitleThemeTextAlignmentCenter": "가운데",
|
||||||
|
"settingsSubtitleThemeTextAlignmentRight": "오른쪽",
|
||||||
|
|
||||||
"settingsSectionPrivacy": "개인정보 보호",
|
"settingsSectionPrivacy": "개인정보 보호",
|
||||||
"settingsEnableAnalytics": "진단 데이터 보내기",
|
"settingsEnableAnalytics": "진단 데이터 보내기",
|
||||||
"settingsSaveSearchHistory": "검색기록",
|
"settingsSaveSearchHistory": "검색기록",
|
||||||
|
|
|
@ -52,7 +52,6 @@ class Durations {
|
||||||
// settings animations
|
// settings animations
|
||||||
static const quickActionListAnimation = Duration(milliseconds: 200);
|
static const quickActionListAnimation = Duration(milliseconds: 200);
|
||||||
static const quickActionHighlightAnimation = Duration(milliseconds: 200);
|
static const quickActionHighlightAnimation = Duration(milliseconds: 200);
|
||||||
static const themeChangeDuration = Duration(milliseconds: 400);
|
|
||||||
|
|
||||||
// delays & refresh intervals
|
// delays & refresh intervals
|
||||||
static const opToastDisplay = Duration(seconds: 3);
|
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 = [
|
static const List<Dependency> dartPackages = [
|
||||||
Dependency(
|
Dependency(
|
||||||
name: 'Collection',
|
name: 'Collection',
|
||||||
|
@ -216,93 +309,6 @@ class Constants {
|
||||||
sourceUrl: 'https://github.com/renggli/dart-xml',
|
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 {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
if (outlineWidth > 0)
|
if (hasOutline)
|
||||||
ImageFiltered(
|
ImageFiltered(
|
||||||
imageFilter: outlineBlurSigma > 0
|
imageFilter: outlineBlurSigma > 0
|
||||||
? ImageFilter.blur(
|
? ImageFilter.blur(
|
||||||
|
@ -46,7 +54,7 @@ class OutlinedText extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Text.rich(
|
Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: textSpans,
|
children: hasOutline ? textSpans.map(_toFillSpan).toList() : textSpans,
|
||||||
),
|
),
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
),
|
),
|
||||||
|
@ -64,4 +72,12 @@ class OutlinedText extends StatelessWidget {
|
||||||
..color = outlineColor,
|
..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/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
import 'package:aves/widgets/settings/common/tile_leading.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:aves/widgets/settings/video/video_actions_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -42,6 +43,7 @@ class VideoSection extends StatelessWidget {
|
||||||
onChanged: (v) => context.read<CollectionSource>().changeFilterVisibility(MimeFilter.video, v),
|
onChanged: (v) => context.read<CollectionSource>().changeFilterVisibility(MimeFilter.video, v),
|
||||||
title: Text(context.l10n.settingsVideoShowVideos),
|
title: Text(context.l10n.settingsVideoShowVideos),
|
||||||
),
|
),
|
||||||
|
VideoActionsTile(),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
value: currentEnableVideoHardwareAcceleration,
|
value: currentEnableVideoHardwareAcceleration,
|
||||||
onChanged: (v) => settings.enableVideoHardwareAcceleration = v,
|
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 ValueNotifier<ViewState> viewStateNotifier;
|
||||||
final bool debugMode;
|
final bool debugMode;
|
||||||
|
|
||||||
static const baseOutlineColor = Colors.black;
|
static const baseShadowOffset = Offset(1, 1);
|
||||||
static const baseShadows = [
|
|
||||||
Shadow(
|
|
||||||
color: Colors.black54,
|
|
||||||
offset: Offset(1, 1),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
const VideoSubtitles({
|
const VideoSubtitles({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -39,6 +33,13 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
builder: (context, settings, child) {
|
builder: (context, settings, child) {
|
||||||
final baseTextAlign = settings.subtitleTextAlignment;
|
final baseTextAlign = settings.subtitleTextAlignment;
|
||||||
final baseOutlineWidth = settings.subtitleShowOutline ? 1 : 0;
|
final baseOutlineWidth = settings.subtitleShowOutline ? 1 : 0;
|
||||||
|
final baseOutlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity);
|
||||||
|
final baseShadows = [
|
||||||
|
Shadow(
|
||||||
|
color: baseOutlineColor,
|
||||||
|
offset: baseShadowOffset,
|
||||||
|
),
|
||||||
|
];
|
||||||
final baseStyle = TextStyle(
|
final baseStyle = TextStyle(
|
||||||
color: settings.subtitleTextColor,
|
color: settings.subtitleTextColor,
|
||||||
backgroundColor: settings.subtitleBackgroundColor,
|
backgroundColor: settings.subtitleBackgroundColor,
|
||||||
|
@ -50,18 +51,6 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
selector: (c, mq) => mq.orientation,
|
selector: (c, mq) => mq.orientation,
|
||||||
builder: (c, orientation, child) {
|
builder: (c, orientation, child) {
|
||||||
final bottom = orientation == Orientation.portrait ? .5 : .8;
|
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;
|
final viewportSize = context.read<MediaQueryData>().size;
|
||||||
|
|
||||||
return ValueListenableBuilder<ViewState>(
|
return ValueListenableBuilder<ViewState>(
|
||||||
|
@ -132,6 +121,7 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
final drawingPaths = extraStyle.drawingPaths;
|
final drawingPaths = extraStyle.drawingPaths;
|
||||||
|
final textAlign = extraStyle.hAlign ?? (position != null ? TextAlign.center : baseTextAlign);
|
||||||
|
|
||||||
Widget child;
|
Widget child;
|
||||||
if (drawingPaths != null) {
|
if (drawingPaths != null) {
|
||||||
|
@ -150,7 +140,7 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
outlineWidth: outlineWidth * (position != null ? viewScale : baseOutlineWidth),
|
outlineWidth: outlineWidth * (position != null ? viewScale : baseOutlineWidth),
|
||||||
outlineColor: extraStyle.borderColor ?? baseOutlineColor,
|
outlineColor: extraStyle.borderColor ?? baseOutlineColor,
|
||||||
outlineBlurSigma: extraStyle.edgeBlur ?? 0,
|
outlineBlurSigma: extraStyle.edgeBlur ?? 0,
|
||||||
textAlign: extraStyle.hAlign ?? baseTextAlign,
|
textAlign: textAlign,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +156,7 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
final textHeight = para.getMaxIntrinsicHeight(double.infinity);
|
final textHeight = para.getMaxIntrinsicHeight(double.infinity);
|
||||||
|
|
||||||
late double anchorOffsetX, anchorOffsetY;
|
late double anchorOffsetX, anchorOffsetY;
|
||||||
switch (extraStyle.hAlign ?? baseTextAlign) {
|
switch (textAlign) {
|
||||||
case TextAlign.left:
|
case TextAlign.left:
|
||||||
anchorOffsetX = 0;
|
anchorOffsetX = 0;
|
||||||
break;
|
break;
|
||||||
|
@ -174,9 +164,7 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
anchorOffsetX = -textWidth;
|
anchorOffsetX = -textWidth;
|
||||||
break;
|
break;
|
||||||
case TextAlign.center:
|
case TextAlign.center:
|
||||||
case TextAlign.start:
|
default:
|
||||||
case TextAlign.end:
|
|
||||||
case TextAlign.justify:
|
|
||||||
anchorOffsetX = -textWidth / 2;
|
anchorOffsetX = -textWidth / 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -227,9 +215,38 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position == null) {
|
if (position == null) {
|
||||||
child = Align(
|
late double alignX;
|
||||||
alignment: toVerticalAlignment(extraStyle),
|
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,
|
child: child,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -292,6 +292,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
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:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
|
@ -31,6 +31,7 @@ dependencies:
|
||||||
firebase_core:
|
firebase_core:
|
||||||
firebase_analytics:
|
firebase_analytics:
|
||||||
firebase_crashlytics:
|
firebase_crashlytics:
|
||||||
|
flex_color_picker:
|
||||||
flutter_highlight:
|
flutter_highlight:
|
||||||
flutter_map:
|
flutter_map:
|
||||||
flutter_markdown:
|
flutter_markdown:
|
||||||
|
|
Loading…
Reference in a new issue