diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 2c8de0c4..04c01cdc 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -310,5 +310,11 @@ "custom_color_filter": "مرشح اللون المخصص", "color_filter_blend_mode": "وضع امتزاج مرشح اللون", "enable_all": "تمكين الكل", - "disable_all": "تعطيل الكل" + "disable_all": "تعطيل الكل", + "font": "الخط", + "color": "اللون", + "font_size": "حجم الخط", + "text": "النص", + "border": "الحدود", + "background": "الخلفية" } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9fdef99c..5dcfbc66 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Benutzerdefinierter Farbfilter", "color_filter_blend_mode": "Farbfilter-Mischmodus", "enable_all": "Alle aktivieren", - "disable_all": "Alle deaktivieren" + "disable_all": "Alle deaktivieren", + "font": "Schriftart", + "color": "Farbe", + "font_size": "Schriftgröße", + "text": "Text", + "border": "Rand", + "background": "Hintergrund" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3e2fbbc9..8f2508a0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -312,5 +312,11 @@ "custom_color_filter": "Custom color filter", "color_filter_blend_mode": "Color filter blend mode", "enable_all": "Enable all", - "disable_all": "Disable all" + "disable_all": "Disable all", + "font": "Font", + "color": "Color", + "font_size": "Font size", + "text": "Text", + "border": "Border", + "background": "Background" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 6efc0f38..e5e34cf3 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Filtro de color personalizado", "color_filter_blend_mode": "Modo de mezcla de filtro de color", "enable_all": "Activar todo", - "disable_all": "Desactivar todo" + "disable_all": "Desactivar todo", + "font": "Fuente", + "color": "Color", + "font_size": "Tamaño de fuente", + "text": "Texto", + "border": "Borde", + "background": "Fondo" } \ No newline at end of file diff --git a/lib/l10n/app_es_419.arb b/lib/l10n/app_es_419.arb index 25bf47d3..db7dda67 100644 --- a/lib/l10n/app_es_419.arb +++ b/lib/l10n/app_es_419.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Filtro de color personalizado", "color_filter_blend_mode": "Modo de mezcla del filtro de color", "enable_all": "Activar todo", - "disable_all": "Desactivar todo" + "disable_all": "Desactivar todo", + "font": "Fuente", + "color": "Color", + "font_size": "Tamaño de fuente", + "text": "Texto", + "border": "Borde", + "background": "Fondo" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d2f1cdf6..59a25feb 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -311,5 +311,11 @@ "custom_color_filter": "Filtre de couleur personnalisé", "color_filter_blend_mode": "Mode de fusion du filtre de couleur", "enable_all": "Activer tout", - "disable_all": "Désactiver tout" + "disable_all": "Désactiver tout", + "font": "Police", + "color": "Couleur", + "font_size": "Taille de police", + "text": "Texte", + "border": "Bordure", + "background": "Arrière-plan" } \ No newline at end of file diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index a81a4fbb..0cb4cd98 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Filter warna kustom", "color_filter_blend_mode": "Mode pencampuran filter warna", "enable_all": "Aktifkan semua", - "disable_all": "Nonaktifkan semua" + "disable_all": "Nonaktifkan semua", + "font": "Jenis Huruf", + "color": "Warna", + "font_size": "Ukuran Huruf", + "text": "Teks", + "border": "Batas", + "background": "Latar Belakang" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 99bebb3e..9f975145 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Filtro colore personalizzato", "color_filter_blend_mode": "Modalità di miscelazione del filtro colore", "enable_all": "Abilita tutto", - "disable_all": "Disabilita tutto" + "disable_all": "Disabilita tutto", + "font": "Carattere", + "color": "Colore", + "font_size": "Dimensione del carattere", + "text": "Testo", + "border": "Bordo", + "background": "Sfondo" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index cc2f3cc4..125e5263 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Filtro de cor personalizado", "color_filter_blend_mode": "Modo de mistura do filtro de cor", "enable_all": "Ativar tudo", - "disable_all": "Desativar tudo" + "disable_all": "Desativar tudo", + "font": "Tipo de letra", + "color": "Cor", + "font_size": "Tamanho da fonte", + "text": "Texto", + "border": "Borda", + "background": "Fundo" } \ No newline at end of file diff --git a/lib/l10n/app_pt_BR.arb b/lib/l10n/app_pt_BR.arb index a6f0d82b..858d8111 100644 --- a/lib/l10n/app_pt_BR.arb +++ b/lib/l10n/app_pt_BR.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Filtro de cor personalizado", "color_filter_blend_mode": "Modo de mistura de filtro de cor", "enable_all": "Ativar todos", - "disable_all": "Desativar todos" + "disable_all": "Desativar todos", + "font": "Fonte", + "color": "Cor", + "font_size": "Tamanho da fonte", + "text": "Texto", + "border": "Borda", + "background": "Fundo" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 18c94e61..c4dff4e3 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Пользовательский цветной фильтр", "color_filter_blend_mode": "Режим смешивания цветового фильтра", "enable_all": "Включить все", - "disable_all": "Отключить все" + "disable_all": "Отключить все", + "font": "Шрифт", + "color": "Цвет", + "font_size": "Размер шрифта", + "text": "Текст", + "border": "Граница", + "background": "Фон" } \ No newline at end of file diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index c429298a..e5ad7421 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -310,5 +310,11 @@ "custom_color_filter": "Özel renk filtresi", "color_filter_blend_mode": "Renk filtresi karışım modu", "enable_all": "Tümünü Etkinleştir", - "disable_all": "Tümünü Devre Dışı Bırak" + "disable_all": "Tümünü Devre Dışı Bırak", + "font": "Yazı Tipi", + "color": "Renk", + "font_size": "Yazı Boyutu", + "text": "Metin", + "border": "Kenarlık", + "background": "Arka Plan" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 6125563a..1cd3fe87 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -312,5 +312,11 @@ "custom_color_filter": "自定义颜色滤镜", "color_filter_blend_mode": "颜色滤镜混合模式", "enable_all": "启用全部", - "disable_all": "禁用全部" + "disable_all": "禁用全部", + "font": "字体", + "color": "颜色", + "font_size": "字号", + "text": "文本", + "border": "边框", + "background": "背景" } \ No newline at end of file diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 2d39de39..81b1d92e 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -167,13 +167,13 @@ class Settings { late CustomColorFilter? customColorFilter; - late PlayerSubtitleSettings? playerSubtitleSettings; - bool? enableCustomColorFilter; @enumerated late ColorFilterBlendMode colorFilterBlendMode; + late PlayerSubtitleSettings? playerSubtitleSettings; + Settings( {this.id = 227, this.displayType = DisplayType.compactGrid, @@ -728,7 +728,7 @@ class CustomColorFilter { @embedded class PlayerSubtitleSettings { - int? size; + int? fontSize; bool? useBold; bool? useItalic; int? textColorA; @@ -744,23 +744,23 @@ class PlayerSubtitleSettings { int? backgroundColorG; int? backgroundColorB; PlayerSubtitleSettings( - {this.size, - this.useBold, - this.useItalic, - this.textColorA, - this.textColorR, - this.textColorG, - this.textColorB, - this.borderColorA, - this.borderColorR, - this.borderColorG, - this.borderColorB, - this.backgroundColorA, - this.backgroundColorR, - this.backgroundColorG, - this.backgroundColorB}); + {this.fontSize = 45, + this.useBold = true, + this.useItalic = false, + this.textColorA = 255, + this.textColorR = 255, + this.textColorG = 255, + this.textColorB = 255, + this.borderColorA = 255, + this.borderColorR = 0, + this.borderColorG = 0, + this.borderColorB = 0, + this.backgroundColorA = 0, + this.backgroundColorR = 0, + this.backgroundColorG = 0, + this.backgroundColorB = 0}); PlayerSubtitleSettings.fromJson(Map json) { - size = json['size']; + fontSize = json['fontSize']; useBold = json['useBold']; useItalic = json['useItalic']; textColorA = json['textColorA']; @@ -778,7 +778,7 @@ class PlayerSubtitleSettings { } Map toJson() => { - 'size': size, + 'fontSize': fontSize, 'useBold': useBold, 'useItalic': useItalic, 'textColorA': textColorA, diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart index a6f26a01..d799e806 100644 --- a/lib/models/settings.g.dart +++ b/lib/models/settings.g.dart @@ -13362,9 +13362,9 @@ const PlayerSubtitleSettingsSchema = Schema( name: r'borderColorR', type: IsarType.long, ), - r'size': PropertySchema( + r'fontSize': PropertySchema( id: 8, - name: r'size', + name: r'fontSize', type: IsarType.long, ), r'textColorA': PropertySchema( @@ -13427,7 +13427,7 @@ void _playerSubtitleSettingsSerialize( writer.writeLong(offsets[5], object.borderColorB); writer.writeLong(offsets[6], object.borderColorG); writer.writeLong(offsets[7], object.borderColorR); - writer.writeLong(offsets[8], object.size); + writer.writeLong(offsets[8], object.fontSize); writer.writeLong(offsets[9], object.textColorA); writer.writeLong(offsets[10], object.textColorB); writer.writeLong(offsets[11], object.textColorG); @@ -13451,7 +13451,7 @@ PlayerSubtitleSettings _playerSubtitleSettingsDeserialize( borderColorB: reader.readLongOrNull(offsets[5]), borderColorG: reader.readLongOrNull(offsets[6]), borderColorR: reader.readLongOrNull(offsets[7]), - size: reader.readLongOrNull(offsets[8]), + fontSize: reader.readLongOrNull(offsets[8]), textColorA: reader.readLongOrNull(offsets[9]), textColorB: reader.readLongOrNull(offsets[10]), textColorG: reader.readLongOrNull(offsets[11]), @@ -14099,63 +14099,63 @@ extension PlayerSubtitleSettingsQueryFilter on QueryBuilder< } QueryBuilder sizeIsNull() { + QAfterFilterCondition> fontSizeIsNull() { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(const FilterCondition.isNull( - property: r'size', + property: r'fontSize', )); }); } QueryBuilder sizeIsNotNull() { + QAfterFilterCondition> fontSizeIsNotNull() { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'size', + property: r'fontSize', )); }); } QueryBuilder sizeEqualTo(int? value) { + QAfterFilterCondition> fontSizeEqualTo(int? value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( - property: r'size', + property: r'fontSize', value: value, )); }); } QueryBuilder sizeGreaterThan( + QAfterFilterCondition> fontSizeGreaterThan( int? value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.greaterThan( include: include, - property: r'size', + property: r'fontSize', value: value, )); }); } QueryBuilder sizeLessThan( + QAfterFilterCondition> fontSizeLessThan( int? value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.lessThan( include: include, - property: r'size', + property: r'fontSize', value: value, )); }); } QueryBuilder sizeBetween( + QAfterFilterCondition> fontSizeBetween( int? lower, int? upper, { bool includeLower = true, @@ -14163,7 +14163,7 @@ extension PlayerSubtitleSettingsQueryFilter on QueryBuilder< }) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.between( - property: r'size', + property: r'fontSize', lower: lower, includeLower: includeLower, upper: upper, diff --git a/lib/modules/anime/anime_player_view.dart b/lib/modules/anime/anime_player_view.dart index 5ef876fa..22945dfe 100644 --- a/lib/modules/anime/anime_player_view.dart +++ b/lib/modules/anime/anime_player_view.dart @@ -8,9 +8,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart' as riv; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/video.dart' as vid; import 'package:mangayomi/modules/anime/providers/anime_player_controller_provider.dart'; +import 'package:mangayomi/modules/anime/providers/state_provider.dart'; import 'package:mangayomi/modules/anime/widgets/aniskip_countdown_btn.dart'; import 'package:mangayomi/modules/anime/widgets/desktop.dart'; import 'package:mangayomi/modules/anime/widgets/mobile.dart'; +import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart'; +import 'package:mangayomi/modules/anime/widgets/subtitle_setting_widget.dart'; import 'package:mangayomi/modules/manga/reader/providers/push_router.dart'; import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart'; import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart'; @@ -363,16 +366,36 @@ class _AnimeStreamPageState extends riv.ConsumerState void _videoSettingDraggableMenu(BuildContext context) async { final l10n = l10nLocalizations(context)!; _player.pause(); - await customDraggableTabBar(tabs: [ - Tab(text: l10n.video_quality), - Tab(text: l10n.video_subtitle), - Tab(text: l10n.video_audio), - ], children: [ - _videoQualityWidget(context), - _videoSubtitle(context), - _videoAudios(context) - ], context: context, vsync: this, fullWidth: true); - + await customDraggableTabBar( + tabs: [ + Tab(text: l10n.video_quality), + Tab(text: l10n.video_subtitle), + Tab(text: l10n.video_audio), + ], + children: [ + _videoQualityWidget(context), + _videoSubtitle(context), + _videoAudios(context) + ], + context: context, + vsync: this, + fullWidth: true, + moreWidget: IconButton( + onPressed: () async { + await customDraggableTabBar(tabs: [ + const Tab(text: "Font"), + const Tab(text: "Color"), + ], children: [ + const FontSettingWidget(), + const ColorSettingWidget() + ], context: context, vsync: this, fullWidth: true); + if (context.mounted) { + Navigator.pop(context); + } + }, + icon: const Icon(Icons.settings_outlined)), + ); + setState(() {}); _player.play(); } @@ -912,33 +935,8 @@ class _AnimeStreamPageState extends riv.ConsumerState return Stack( children: [ Video( - subtitleViewConfiguration: const SubtitleViewConfiguration( - style: TextStyle( - fontSize: 50, - fontWeight: FontWeight.bold, - color: Colors.white, - fontFamily: "", - shadows: [ - Shadow( - // bottomLeft - offset: Offset(-2.5, -2.5), - color: Colors.black), - Shadow( - // bottomRight - offset: Offset(2.5, -2.5), - color: Colors.black), - Shadow( - // topRight - offset: Offset(2.5, 2.5), - color: Colors.black), - Shadow( - // topLeft - offset: Offset(-2.5, 2.5), - color: Colors.black), - Shadow(offset: Offset(0.2, 0.0), blurRadius: 9.0) - ], - backgroundColor: Colors.transparent), - ), + subtitleViewConfiguration: SubtitleViewConfiguration( + visible: false, style: subtileTextStyle(ref)), fit: fit, key: _key, controls: (state) => isDesktop @@ -1046,6 +1044,14 @@ class _AnimeStreamPageState extends riv.ConsumerState }, ), ), + Positioned( + child: IgnorePointer( + child: CustomSubtitleView( + controller: _controller, + configuration: + SubtitleViewConfiguration(style: subtileTextStyle(ref)), + ), + )) ], ); } @@ -1078,6 +1084,29 @@ Widget seekIndicatorTextWidget(Duration duration, Duration currentPosition) { ); } +TextStyle subtileTextStyle(WidgetRef ref) { + final set = ref.watch(subtitleSettingsStateProvider); + final borderColor = Color.fromARGB(set.borderColorA!, set.borderColorR!, + set.borderColorG!, set.borderColorB!); + return TextStyle( + fontSize: set.fontSize!.toDouble(), + fontWeight: set.useBold! ? FontWeight.bold : null, + fontStyle: set.useItalic! ? FontStyle.italic : null, + color: Color.fromARGB( + set.textColorA!, set.textColorR!, set.textColorG!, set.textColorB!), + fontFamily: "", + shadows: [ + Shadow( + offset: const Offset(-2, -2), color: borderColor, blurRadius: 1.5), + Shadow( + offset: const Offset(2, -2), color: borderColor, blurRadius: 1.5), + Shadow(offset: const Offset(2, 2), color: borderColor, blurRadius: 1.5), + Shadow(offset: const Offset(-2, 2), color: borderColor, blurRadius: 1.5) + ], + backgroundColor: Color.fromARGB(set.backgroundColorA!, + set.backgroundColorR!, set.backgroundColorG!, set.backgroundColorB!)); +} + class VideoPrefs { String? title; VideoTrack? videoTrack; diff --git a/lib/modules/anime/providers/state_provider.dart b/lib/modules/anime/providers/state_provider.dart new file mode 100644 index 00000000..7506221e --- /dev/null +++ b/lib/modules/anime/providers/state_provider.dart @@ -0,0 +1,39 @@ +import 'package:mangayomi/main.dart'; +import 'package:mangayomi/models/settings.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +part 'state_provider.g.dart'; + +@riverpod +class SubtitleSettingsState extends _$SubtitleSettingsState { + @override + PlayerSubtitleSettings build() { + final subSets = isar.settings.getSync(227)!.playerSubtitleSettings; + if (subSets == null) { + set(PlayerSubtitleSettings(), true); + return PlayerSubtitleSettings(); + } + return subSets; + } + + void set(PlayerSubtitleSettings value, bool end) { + final settings = isar.settings.getSync(227); + state = value; + if (end) { + isar.writeTxnSync(() => + isar.settings.putSync(settings!..playerSubtitleSettings = value)); + } + } + + void resetColor() { + final settings = isar.settings.getSync(227); + state = PlayerSubtitleSettings( + fontSize: state.fontSize, + useBold: state.useBold, + useItalic: state.useItalic); + isar.writeTxnSync(() => isar.settings.putSync(settings! + ..playerSubtitleSettings = PlayerSubtitleSettings( + fontSize: state.fontSize, + useBold: state.useBold, + useItalic: state.useItalic))); + } +} diff --git a/lib/modules/anime/providers/state_provider.g.dart b/lib/modules/anime/providers/state_provider.g.dart new file mode 100644 index 00000000..da643c18 --- /dev/null +++ b/lib/modules/anime/providers/state_provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'state_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$subtitleSettingsStateHash() => + r'4f668c79675772a76d80585db43d041675d0d178'; + +/// See also [SubtitleSettingsState]. +@ProviderFor(SubtitleSettingsState) +final subtitleSettingsStateProvider = AutoDisposeNotifierProvider< + SubtitleSettingsState, PlayerSubtitleSettings>.internal( + SubtitleSettingsState.new, + name: r'subtitleSettingsStateProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$subtitleSettingsStateHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$SubtitleSettingsState = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/modules/anime/widgets/subtitle_setting_widget.dart b/lib/modules/anime/widgets/subtitle_setting_widget.dart new file mode 100644 index 00000000..d6a11988 --- /dev/null +++ b/lib/modules/anime/widgets/subtitle_setting_widget.dart @@ -0,0 +1,298 @@ +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mangayomi/modules/anime/providers/state_provider.dart'; +import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart'; +import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.dart'; +import 'package:mangayomi/providers/l10n_providers.dart'; +import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; + +class FontSettingWidget extends ConsumerStatefulWidget { + const FontSettingWidget({super.key}); + + @override + ConsumerState createState() => _FontSettingWidgetState(); +} + +class _FontSettingWidgetState extends ConsumerState { + @override + Widget build(BuildContext context) { + final subtitleSettings = ref.watch(subtitleSettingsStateProvider); + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + iconButton(Icons.remove, () { + ref.read(subtitleSettingsStateProvider.notifier).set( + subtitleSettings..fontSize = subtitleSettings.fontSize! - 1, + true); + setState(() {}); + }, + backgroundColor: context.dynamicWhiteBlackColor, + iconColors: context.isLight ? Colors.white : Colors.black, + size: 25), + SizedBox( + width: 200, + child: TextFormField( + controller: TextEditingController( + text: subtitleSettings.fontSize.toString()), + keyboardType: TextInputType.number, + onChanged: (v) { + final val = int.tryParse(v); + if (val != null) { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subtitleSettings..fontSize = val, true); + } + }, + decoration: InputDecoration( + labelText: context.l10n.font_size, + isDense: true, + filled: true, + fillColor: Colors.transparent, + enabledBorder: OutlineInputBorder( + borderSide: + BorderSide(color: context.dynamicThemeColor)), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: context.dynamicThemeColor)), + border: OutlineInputBorder( + borderSide: + BorderSide(color: context.dynamicThemeColor))), + ), + ), + iconButton(Icons.add, () { + ref.read(subtitleSettingsStateProvider.notifier).set( + subtitleSettings..fontSize = subtitleSettings.fontSize! + 1, + true); + setState(() {}); + }, + backgroundColor: context.dynamicWhiteBlackColor, + iconColors: context.isLight ? Colors.white : Colors.black, + size: 25), + iconButton(Icons.format_bold, () { + ref.read(subtitleSettingsStateProvider.notifier).set( + subtitleSettings..useBold = !subtitleSettings.useBold!, + true); + setState(() {}); + }, + iconColors: subtitleSettings.useBold! + ? null + : context.dynamicWhiteBlackColor.withOpacity(0.5)), + iconButton(Icons.format_italic, () { + ref.read(subtitleSettingsStateProvider.notifier).set( + subtitleSettings..useItalic = !subtitleSettings.useItalic!, + true); + setState(() {}); + }, + iconColors: subtitleSettings.useItalic! + ? null + : context.dynamicWhiteBlackColor.withOpacity(0.5)), + ], + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text("Lorem ipsum dolor sit amet", + style: subtileTextStyle(ref).copyWith(fontSize: 22), + textAlign: TextAlign.center), + ), + TextButton( + onPressed: () { + ref.read(subtitleSettingsStateProvider.notifier).set( + subtitleSettings + ..useItalic = false + ..useBold = false + ..fontSize = 45, + true); + setState(() {}); + }, + child: Text(context.l10n.reset)) + ], + ), + ); + } +} + +class ColorSettingWidget extends ConsumerStatefulWidget { + const ColorSettingWidget({super.key}); + + @override + ConsumerState createState() => _ColorSettingWidgetState(); +} + +class _ColorSettingWidgetState extends ConsumerState { + String selector = "text"; + + Widget button(String text, Color color) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.all(0), + backgroundColor: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + elevation: 0, + shadowColor: Colors.transparent), + onPressed: () { + setState(() { + selector = text.toLowerCase(); + }); + }, + child: Column( + children: [ + Text(text, style: TextStyle(color: context.textColor)), + Padding( + padding: const EdgeInsets.all(2), + child: Container( + height: 25, + width: 25, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: color, + border: Border.all( + width: 2, color: context.dynamicWhiteBlackColor)), + ), + ), + Text("#${color.hexCode}", style: TextStyle(color: context.textColor)), + Icon( + Icons.arrow_drop_down, + color: selector != text.toLowerCase() + ? Colors.transparent + : context.textColor, + ) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + final subSets = ref.watch(subtitleSettingsStateProvider); + final textColor = Color.fromARGB(subSets.textColorA!, subSets.textColorR!, + subSets.textColorG!, subSets.textColorB!); + final borderColor = Color.fromARGB(subSets.borderColorA!, + subSets.borderColorR!, subSets.borderColorG!, subSets.borderColorB!); + final backgroundColor = Color.fromARGB( + subSets.backgroundColorA!, + subSets.backgroundColorR!, + subSets.backgroundColorG!, + subSets.backgroundColorB!); + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + children: [ + Expanded(flex: 3, child: button(context.l10n.text, textColor)), + Expanded( + flex: 3, child: button(context.l10n.border, borderColor)), + Expanded( + flex: 3, + child: button(context.l10n.background, backgroundColor)), + ], + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text("Lorem ipsum dolor sit amet", + style: subtileTextStyle(ref).copyWith(fontSize: 22), + textAlign: TextAlign.center), + ), + if (selector == "text") ...[ + rgbaFilterWidget(subSets.textColorA!, subSets.textColorR!, + subSets.textColorG!, subSets.textColorB!, (val) { + if (val.$3 == "r") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..textColorR = val.$1.toInt(), val.$2); + } else if (val.$3 == "g") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..textColorG = val.$1.toInt(), val.$2); + } else if (val.$3 == "b") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..textColorB = val.$1.toInt(), val.$2); + } else { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..textColorA = val.$1.toInt(), val.$2); + } + setState(() {}); + }, context), + ] else if (selector == "border") ...[ + rgbaFilterWidget(subSets.borderColorA!, subSets.borderColorR!, + subSets.borderColorG!, subSets.borderColorB!, (val) { + if (val.$3 == "r") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..borderColorR = val.$1.toInt(), val.$2); + } else if (val.$3 == "g") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..borderColorG = val.$1.toInt(), val.$2); + } else if (val.$3 == "b") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..borderColorB = val.$1.toInt(), val.$2); + } else { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..borderColorA = val.$1.toInt(), val.$2); + } + setState(() {}); + }, context), + ] else ...[ + rgbaFilterWidget( + subSets.backgroundColorA!, + subSets.backgroundColorR!, + subSets.backgroundColorG!, + subSets.backgroundColorB!, (val) { + if (val.$3 == "r") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..backgroundColorR = val.$1.toInt(), val.$2); + } else if (val.$3 == "g") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..backgroundColorG = val.$1.toInt(), val.$2); + } else if (val.$3 == "b") { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..backgroundColorB = val.$1.toInt(), val.$2); + } else { + ref + .read(subtitleSettingsStateProvider.notifier) + .set(subSets..backgroundColorA = val.$1.toInt(), val.$2); + } + setState(() {}); + }, context), + ], + TextButton( + onPressed: () { + ref.read(subtitleSettingsStateProvider.notifier).resetColor(); + setState(() {}); + }, + child: Text(context.l10n.reset)) + ], + ), + ); + } +} + +Widget iconButton(IconData icon, void Function()? onPressed, + {Color? backgroundColor, Color? iconColors, double size = 35}) => + Padding( + padding: const EdgeInsets.all(5), + child: SizedBox( + height: size, + width: size, + child: IconButton( + iconSize: size * 0.9, + style: ButtonStyle( + backgroundColor: MaterialStatePropertyAll(backgroundColor)), + padding: const EdgeInsets.all(1), + onPressed: onPressed, + icon: Icon(icon, color: iconColors)), + ), + ); diff --git a/lib/modules/anime/widgets/subtitle_view.dart b/lib/modules/anime/widgets/subtitle_view.dart new file mode 100644 index 00000000..e1febaf1 --- /dev/null +++ b/lib/modules/anime/widgets/subtitle_view.dart @@ -0,0 +1,133 @@ +/// This file is a part of media_kit (https://github.com/media-kit/media-kit). +/// +/// Copyright © 2021 & onwards, Hitesh Kumar Saini . +/// All rights reserved. +/// Use of this source code is governed by MIT license that can be found in the LICENSE file. +// ignore_for_file: dangling_library_doc_comments, doc_directive_missing_closing_tag, deprecated_member_use + +import 'dart:async'; +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mangayomi/modules/anime/providers/state_provider.dart'; +import 'package:media_kit_video/media_kit_video.dart'; + +class CustomSubtitleView extends ConsumerStatefulWidget { + final VideoController controller; + final SubtitleViewConfiguration configuration; + + const CustomSubtitleView({ + super.key, + required this.controller, + required this.configuration, + }); + + @override + ConsumerState createState() => _CustomSubtitleViewState(); +} + +class _CustomSubtitleViewState extends ConsumerState { + late List subtitle = widget.controller.player.state.subtitle; + late TextStyle style = widget.configuration.style; + late TextAlign textAlign = widget.configuration.textAlign; + late EdgeInsets padding = widget.configuration.padding; + late Duration duration = const Duration(milliseconds: 100); + + StreamSubscription>? subscription; + + static const kTextScaleFactorReferenceWidth = 1920.0; + static const kTextScaleFactorReferenceHeight = 1080.0; + + @override + void initState() { + subscription = widget.controller.player.stream.subtitle.listen((value) { + setState(() { + subtitle = value; + }); + }); + super.initState(); + } + + @override + void dispose() { + subscription?.cancel(); + super.dispose(); + } + + void setPadding( + EdgeInsets padding, { + Duration duration = const Duration(milliseconds: 100), + }) { + if (this.duration != duration) { + setState(() { + this.duration = duration; + }); + } + setState(() { + this.padding = padding; + }); + } + + @override + Widget build(BuildContext context) { + subtitle = widget.controller.player.state.subtitle; + style = widget.configuration.style; + textAlign = widget.configuration.textAlign; + padding = widget.configuration.padding; + return LayoutBuilder( + builder: (context, constraints) { + final textScaleFactor = widget.configuration.textScaleFactor ?? + MediaQuery.of(context).textScaleFactor * + sqrt( + ((constraints.maxWidth * constraints.maxHeight) / + (kTextScaleFactorReferenceWidth * + kTextScaleFactorReferenceHeight)) + .clamp(0.0, 1.0), + ); + return Material( + color: Colors.transparent, + child: AnimatedContainer( + padding: padding, + duration: duration, + alignment: Alignment.bottomCenter, + child: Text( + [ + for (final line in subtitle) + if (line.trim().isNotEmpty) line.trim(), + ].join('\n'), + style: subtileTextStyle(ref), + textAlign: textAlign, + textScaleFactor: textScaleFactor, + ), + ), + ); + }, + ); + } +} + +TextStyle subtileTextStyle(WidgetRef ref) { + final subSets = ref.watch(subtitleSettingsStateProvider); + final borderColor = Color.fromARGB(subSets.borderColorA!, + subSets.borderColorR!, subSets.borderColorG!, subSets.borderColorB!); + return TextStyle( + fontSize: subSets.fontSize!.toDouble(), + fontWeight: subSets.useBold! ? FontWeight.bold : null, + fontStyle: subSets.useItalic! ? FontStyle.italic : null, + color: Color.fromARGB(subSets.textColorA!, subSets.textColorR!, + subSets.textColorG!, subSets.textColorB!), + fontFamily: "", + shadows: [ + Shadow( + offset: const Offset(-3, -3), color: borderColor), + Shadow( + offset: const Offset(3, -3), color: borderColor), + Shadow(offset: const Offset(3, 3), color: borderColor), + Shadow(offset: const Offset(-3, 3), color: borderColor) + ], + backgroundColor: Color.fromARGB( + subSets.backgroundColorA!, + subSets.backgroundColorR!, + subSets.backgroundColorG!, + subSets.backgroundColorB!)); +} diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index de596b07..4a37d603 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -2202,25 +2202,24 @@ class _MangaChapterPageGalleryState .set(val); }, children: [ - customColorFilterListTile(isDesktop, "r", r, (val) { - ref - .read(customColorFilterStateProvider.notifier) - .set(a, val.$1.toInt(), g, b, val.$2); - }, context), - customColorFilterListTile(isDesktop, "g", g, (val) { - ref - .read(customColorFilterStateProvider.notifier) - .set(a, r, val.$1.toInt(), b, val.$2); - }, context), - customColorFilterListTile(isDesktop, "b", b, (val) { - ref - .read(customColorFilterStateProvider.notifier) - .set(a, r, g, val.$1.toInt(), val.$2); - }, context), - customColorFilterListTile(isDesktop, "a", a, (val) { - ref - .read(customColorFilterStateProvider.notifier) - .set(val.$1.toInt(), r, g, b, val.$2); + rgbaFilterWidget(a, r, g, b, (val) { + if (val.$3 == "r") { + ref + .read(customColorFilterStateProvider.notifier) + .set(a, val.$1.toInt(), g, b, val.$2); + } else if (val.$3 == "g") { + ref + .read(customColorFilterStateProvider.notifier) + .set(a, r, val.$1.toInt(), b, val.$2); + } else if (val.$3 == "b") { + ref + .read(customColorFilterStateProvider.notifier) + .set(a, r, g, val.$1.toInt(), val.$2); + } else { + ref + .read(customColorFilterStateProvider.notifier) + .set(val.$1.toInt(), r, g, b, val.$2); + } }, context), CustomPopupMenuButton( label: l10n.color_filter_blend_mode, diff --git a/lib/modules/manga/reader/widgets/color_filter_widget.dart b/lib/modules/manga/reader/widgets/color_filter_widget.dart index 1b81a903..b00b0150 100644 --- a/lib/modules/manga/reader/widgets/color_filter_widget.dart +++ b/lib/modules/manga/reader/widgets/color_filter_widget.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mangayomi/modules/manga/reader/providers/color_filter_provider.dart'; import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart'; +import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; class ColorFilterWidget extends ConsumerWidget { final Widget child; @@ -27,39 +28,72 @@ class ColorFilterWidget extends ConsumerWidget { } } -Widget customColorFilterListTile(bool isDesktop, String label, int value, - void Function((double, bool))? onChanged, BuildContext context) { +Widget customColorFilterListTile(String label, int value, + void Function((double, bool, String))? onChanged, BuildContext context) { + final color = switch (label) { + "a" => Color.fromARGB(value, 255, 255, 255), + "r" => Color.fromARGB(255, value, 0, 0), + "g" => Color.fromARGB(255, 0, value, 0), + _ => Color.fromARGB(255, 0, 0, value), + }; + return Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Row( children: [ - SizedBox( - width: 50, - child: Column( - children: [ - Text(label.toUpperCase(), - style: const TextStyle( - fontSize: 10, fontWeight: FontWeight.bold)), - Text("$value"), - ], + Padding( + padding: const EdgeInsets.only(right: 10), + child: SizedBox( + width: 65, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label.toUpperCase(), + style: const TextStyle( + fontSize: 17, fontWeight: FontWeight.bold)), + Container( + height: 25, + width: 25, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: color, + border: Border.all( + width: 2, color: context.dynamicWhiteBlackColor)), + ) + ], + ), ), ), Expanded( child: SliderTheme( data: SliderTheme.of(context).copyWith( - trackHeight: isDesktop ? null : 3, + trackHeight: context.isDesktop ? null : 3, overlayShape: const RoundSliderOverlayShape(overlayRadius: 5.0)), child: Slider( min: 0.0, max: 255, divisions: max(244, 1), - onChangeEnd: (value) => onChanged!.call((value, false)), + onChangeEnd: (value) => onChanged!.call((value, false, label)), value: value.toDouble(), - onChanged: (value) => onChanged!.call((value, true)), + onChanged: (value) => onChanged!.call((value, true, label)), )), ), + Padding( + padding: const EdgeInsets.all(2), + child: Text("$value"), + ), ], ), ); } + +Widget rgbaFilterWidget(int a, int r, int g, int b, + void Function((double, bool, String))? onChanged, BuildContext context) { + return Column(children: [ + customColorFilterListTile("r", r, onChanged, context), + customColorFilterListTile("g", g, onChanged, context), + customColorFilterListTile("b", b, onChanged, context), + customColorFilterListTile("a", a, onChanged, context), + ]); +} diff --git a/lib/modules/more/about/providers/check_for_update.dart b/lib/modules/more/about/providers/check_for_update.dart index 95ad55c6..3a25f221 100644 --- a/lib/modules/more/about/providers/check_for_update.dart +++ b/lib/modules/more/about/providers/check_for_update.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:developer'; import 'package:bot_toast/bot_toast.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; @@ -20,7 +21,10 @@ checkForUpdate(CheckForUpdateRef ref, BotToast.showText(text: l10n.searching_for_updates); } final info = await PackageInfo.fromPlatform(); - log(info.data.toString()); + if (kDebugMode) { + log(info.data.toString()); + } + final updateAvailable = await _checkUpdate(); if (compareVersions(info.version, updateAvailable.$1) < 0) { if (manualUpdate) { diff --git a/lib/modules/more/about/providers/check_for_update.g.dart b/lib/modules/more/about/providers/check_for_update.g.dart index 4a889b1a..52892eeb 100644 --- a/lib/modules/more/about/providers/check_for_update.g.dart +++ b/lib/modules/more/about/providers/check_for_update.g.dart @@ -6,7 +6,7 @@ part of 'check_for_update.dart'; // RiverpodGenerator // ************************************************************************** -String _$checkForUpdateHash() => r'07a3b5c85180261e8040064974c668e4fe5dbfcc'; +String _$checkForUpdateHash() => r'866b89bb48a4f37e240d2c272215b8646790d1b0'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/modules/widgets/custom_draggable_tabbar.dart b/lib/modules/widgets/custom_draggable_tabbar.dart index d8f0d1ce..1d650b18 100644 --- a/lib/modules/widgets/custom_draggable_tabbar.dart +++ b/lib/modules/widgets/custom_draggable_tabbar.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:draggable_menu/draggable_menu.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -36,11 +38,12 @@ Future customDraggableTabBar( required List children, required BuildContext context, required TickerProvider vsync, - bool fullWidth = false}) async { + bool fullWidth = false, + Widget? moreWidget}) async { final controller = DraggableMenuController(); late TabController tabBarController; tabBarController = TabController(length: tabs.length, vsync: vsync); - final maxHeight = context.mediaHeight(0.8).toInt(); + final maxHeight = context.mediaHeight(0.8); int index = 0; List> widgetsHeight = []; @@ -61,7 +64,6 @@ Future customDraggableTabBar( } }); - double additionnalHeight = 0.1; await showDialog( context: context, builder: (context) { @@ -71,10 +73,13 @@ Future customDraggableTabBar( for (var i = 0; i < children.length; i++) ...[ MeasureWidgetSize( onCalculateSize: (size) { + final additionnalHeight = + ((List.generate(10000, (index) => index * 0.0001)) + ..shuffle()) + .first; double newHeight = size!.height + 52.0 + additionnalHeight; - additionnalHeight += 0.1; if (!(newHeight <= maxHeight)) { - newHeight = maxHeight - 80; + newHeight = maxHeight + additionnalHeight; } widgetsHeight.add({"index": i, "height": newHeight}); if (widgetsHeight.length == children.length) { @@ -88,6 +93,7 @@ Future customDraggableTabBar( ); }, ); + log(widgetsHeight.toString()); widgetsHeight .sort((a, b) => (a["height"] as double).compareTo(b["height"] as double)); if (context.mounted) { @@ -127,16 +133,46 @@ Future customDraggableTabBar( child: DefaultTabController( length: tabs.length, child: Column(children: [ - TabBar( - unselectedLabelStyle: - const TextStyle(fontWeight: FontWeight.w500), - labelStyle: - const TextStyle(fontWeight: FontWeight.bold), - dividerColor: - context.isLight ? Colors.black : Colors.grey, - dividerHeight: 0.7, - controller: tabBarController, - tabs: tabs), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Flexible( + flex: 9, + child: TabBar( + unselectedLabelStyle: const TextStyle( + fontWeight: FontWeight.w500), + labelStyle: const TextStyle( + fontWeight: FontWeight.bold), + dividerColor: context.isLight + ? Colors.black + : Colors.grey, + dividerHeight: 0.4, + controller: tabBarController, + tabs: tabs), + ), + if (moreWidget != null) + Flexible( + flex: 1, + child: Column( + children: [ + moreWidget, + const SizedBox(height: 2), + Row( + children: [ + Flexible( + child: Container( + color: context.isLight + ? Colors.black + : Colors.grey, + height: 0.4), + ), + ], + ) + ], + ), + ) + ], + ), Flexible( child: TabBarView( controller: tabBarController, diff --git a/lib/utils/extensions/build_context_extensions.dart b/lib/utils/extensions/build_context_extensions.dart index 292ac648..b9e1277b 100644 --- a/lib/utils/extensions/build_context_extensions.dart +++ b/lib/utils/extensions/build_context_extensions.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; extension BuildContextExtensions on BuildContext { @@ -9,6 +11,26 @@ extension BuildContextExtensions on BuildContext { return Theme.of(this).primaryColor; } + Color get dynamicThemeColor { + return isLight ? secondaryColor : primaryColor; + } + + Color get dynamicWhiteBlackColor { + return isLight ? Colors.black : Colors.white; + } + + bool get isDesktop { + return Platform.isMacOS || Platform.isLinux || Platform.isWindows; + } + + bool get isMbile { + return Platform.isIOS || Platform.isAndroid; + } + + Color get textColor { + return themeData.textTheme.bodyLarge!.color!; + } + Color get secondaryColor { return Theme.of(this).iconTheme.color!.withOpacity(0.7); } @@ -25,10 +47,6 @@ extension BuildContextExtensions on BuildContext { return MediaQuery.of(this).size.width * data; } - bool get isDesktop { - return MediaQuery.of(this).size.width >= 1200; - } - bool get isTablet { return MediaQuery.of(this).size.width >= 600; }