mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 17:25:32 +00:00
added subtitle setting feature
This commit is contained in:
parent
341486b004
commit
69e4a67f38
26 changed files with 835 additions and 140 deletions
|
|
@ -310,5 +310,11 @@
|
|||
"custom_color_filter": "مرشح اللون المخصص",
|
||||
"color_filter_blend_mode": "وضع امتزاج مرشح اللون",
|
||||
"enable_all": "تمكين الكل",
|
||||
"disable_all": "تعطيل الكل"
|
||||
"disable_all": "تعطيل الكل",
|
||||
"font": "الخط",
|
||||
"color": "اللون",
|
||||
"font_size": "حجم الخط",
|
||||
"text": "النص",
|
||||
"border": "الحدود",
|
||||
"background": "الخلفية"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -310,5 +310,11 @@
|
|||
"custom_color_filter": "Пользовательский цветной фильтр",
|
||||
"color_filter_blend_mode": "Режим смешивания цветового фильтра",
|
||||
"enable_all": "Включить все",
|
||||
"disable_all": "Отключить все"
|
||||
"disable_all": "Отключить все",
|
||||
"font": "Шрифт",
|
||||
"color": "Цвет",
|
||||
"font_size": "Размер шрифта",
|
||||
"text": "Текст",
|
||||
"border": "Граница",
|
||||
"background": "Фон"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -312,5 +312,11 @@
|
|||
"custom_color_filter": "自定义颜色滤镜",
|
||||
"color_filter_blend_mode": "颜色滤镜混合模式",
|
||||
"enable_all": "启用全部",
|
||||
"disable_all": "禁用全部"
|
||||
"disable_all": "禁用全部",
|
||||
"font": "字体",
|
||||
"color": "颜色",
|
||||
"font_size": "字号",
|
||||
"text": "文本",
|
||||
"border": "边框",
|
||||
"background": "背景"
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
39
lib/modules/anime/providers/state_provider.dart
Normal file
39
lib/modules/anime/providers/state_provider.dart
Normal 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)));
|
||||
}
|
||||
}
|
||||
27
lib/modules/anime/providers/state_provider.g.dart
Normal file
27
lib/modules/anime/providers/state_provider.g.dart
Normal 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
|
||||
298
lib/modules/anime/widgets/subtitle_setting_widget.dart
Normal file
298
lib/modules/anime/widgets/subtitle_setting_widget.dart
Normal 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)),
|
||||
),
|
||||
);
|
||||
133
lib/modules/anime/widgets/subtitle_view.dart
Normal file
133
lib/modules/anime/widgets/subtitle_view.dart
Normal 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!));
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue