added subtitle setting feature

This commit is contained in:
kodjomoustapha 2024-02-22 17:55:50 +01:00
parent 341486b004
commit 69e4a67f38
26 changed files with 835 additions and 140 deletions

View file

@ -310,5 +310,11 @@
"custom_color_filter": "مرشح اللون المخصص",
"color_filter_blend_mode": "وضع امتزاج مرشح اللون",
"enable_all": "تمكين الكل",
"disable_all": "تعطيل الكل"
"disable_all": "تعطيل الكل",
"font": "الخط",
"color": "اللون",
"font_size": "حجم الخط",
"text": "النص",
"border": "الحدود",
"background": "الخلفية"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -310,5 +310,11 @@
"custom_color_filter": "Пользовательский цветной фильтр",
"color_filter_blend_mode": "Режим смешивания цветового фильтра",
"enable_all": "Включить все",
"disable_all": "Отключить все"
"disable_all": "Отключить все",
"font": "Шрифт",
"color": "Цвет",
"font_size": "Размер шрифта",
"text": "Текст",
"border": "Граница",
"background": "Фон"
}

View file

@ -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"
}

View file

@ -312,5 +312,11 @@
"custom_color_filter": "自定义颜色滤镜",
"color_filter_blend_mode": "颜色滤镜混合模式",
"enable_all": "启用全部",
"disable_all": "禁用全部"
"disable_all": "禁用全部",
"font": "字体",
"color": "颜色",
"font_size": "字号",
"text": "文本",
"border": "边框",
"background": "背景"
}

View file

@ -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<String, dynamic> json) {
size = json['size'];
fontSize = json['fontSize'];
useBold = json['useBold'];
useItalic = json['useItalic'];
textColorA = json['textColorA'];
@ -778,7 +778,7 @@ class PlayerSubtitleSettings {
}
Map<String, dynamic> toJson() => {
'size': size,
'fontSize': fontSize,
'useBold': useBold,
'useItalic': useItalic,
'textColorA': textColorA,

View file

@ -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<PlayerSubtitleSettings, PlayerSubtitleSettings,
QAfterFilterCondition> sizeIsNull() {
QAfterFilterCondition> fontSizeIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'size',
property: r'fontSize',
));
});
}
QueryBuilder<PlayerSubtitleSettings, PlayerSubtitleSettings,
QAfterFilterCondition> sizeIsNotNull() {
QAfterFilterCondition> fontSizeIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'size',
property: r'fontSize',
));
});
}
QueryBuilder<PlayerSubtitleSettings, PlayerSubtitleSettings,
QAfterFilterCondition> 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<PlayerSubtitleSettings, PlayerSubtitleSettings,
QAfterFilterCondition> 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<PlayerSubtitleSettings, PlayerSubtitleSettings,
QAfterFilterCondition> 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<PlayerSubtitleSettings, PlayerSubtitleSettings,
QAfterFilterCondition> 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,

View file

@ -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<AnimeStreamPage>
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<AnimeStreamPage>
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<AnimeStreamPage>
},
),
),
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;

View file

@ -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)));
}
}

View file

@ -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<PlayerSubtitleSettings>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View file

@ -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<FontSettingWidget> createState() => _FontSettingWidgetState();
}
class _FontSettingWidgetState extends ConsumerState<FontSettingWidget> {
@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<ColorSettingWidget> createState() => _ColorSettingWidgetState();
}
class _ColorSettingWidgetState extends ConsumerState<ColorSettingWidget> {
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)),
),
);

View file

@ -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 <saini123hitesh@gmail.com>.
/// 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<CustomSubtitleView> createState() => _CustomSubtitleViewState();
}
class _CustomSubtitleViewState extends ConsumerState<CustomSubtitleView> {
late List<String> 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<List<String>>? 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!));
}

View file

@ -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<ColorFilterBlendMode>(
label: l10n.color_filter_blend_mode,

View file

@ -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),
]);
}

View file

@ -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) {

View file

@ -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 {

View file

@ -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<void> customDraggableTabBar(
required List<Widget> 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<Map<String, dynamic>> widgetsHeight = [];
@ -61,7 +64,6 @@ Future<void> customDraggableTabBar(
}
});
double additionnalHeight = 0.1;
await showDialog(
context: context,
builder: (context) {
@ -71,10 +73,13 @@ Future<void> 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<void> 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<void> 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,

View file

@ -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;
}