added more player customizations

- added navigation button if an entry is already being tracked
- added option to calibrate soft subtitles
This commit is contained in:
Schnitzel5 2025-07-31 01:58:18 +02:00
parent d7c59fe16b
commit afee5926dc
33 changed files with 3720 additions and 993 deletions

View file

@ -335,6 +335,7 @@
"installed": "Installed",
"auto_scroll": "Auto scroll",
"video_audio": "Audio",
"video_audio_info": "Preferred languages, pitch correction, audio channels",
"player": "Player",
"markEpisodeAsSeenSetting": "At what point to mark the episode as seen",
"default_skip_intro_length": "Default Skip intro length",
@ -406,6 +407,7 @@
"torrent_url": "Torrent url",
"or": "OR",
"advanced": "Advanced",
"advanced_info": "mpv config",
"use_native_http_client": "Use native http client",
"use_native_http_client_info": "it automatically supports platform features such VPNs, support more HTTP features such as HTTP/3 and custom redirect handling",
"n_hour_ago": "{hour} hour ago",
@ -458,6 +460,7 @@
"you_have_finished_reading": "You have finished reading",
"return_to_the_list_of_chapters": "Return to the list of chapters",
"hwdec": "Hardware Decoder",
"track_library_navigate": "Go to existing local entry",
"track_library_add": "Add to local library",
"track_library_add_confirm": "Add tracked item to local library",
"track_library_not_logged": "Login to the corresponding tracker to use this feature!",
@ -488,5 +491,23 @@
"custom_buttons_js_code_req": "Javascript code required",
"custom_buttons_js_code_long": "Javascript code (on long press)",
"custom_buttons_startup": "Javascript code (on startup)",
"n_days": "{n} days"
"n_days": "{n} days",
"decoder": "Decoder",
"decoder_info": "Hardware decoding, pixel format, debanding",
"enable_gpu_next": "Enable gpu-next (Android only)",
"enable_gpu_next_info": "A new video rendering backend",
"debanding": "Debanding",
"use_yuv420p": "Use YUV420P pixel format",
"use_yuv420p_info": "May fix black screens on some video codecs, can also improve performance at the cost of quality",
"audio_preferred_languages": "Preferred langauages",
"audio_preferred_languages_info": "Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.",
"enable_audio_pitch_correction": "Enable audio pitch correction",
"enable_audio_pitch_correction_info": "Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds",
"audio_channels": "Audio channels",
"volume_boost_cap": "Volume boost cap",
"internal_player": "Internal player",
"internal_player_info": "Progress, controls, orientation",
"subtitle_delay_text": "Subtitle delay",
"subtitle_delay": "Delay (ms)",
"subtitle_speed": "Speed"
}

View file

@ -2081,6 +2081,12 @@ abstract class AppLocalizations {
/// **'Audio'**
String get video_audio;
/// No description provided for @video_audio_info.
///
/// In en, this message translates to:
/// **'Preferred languages, pitch correction, audio channels'**
String get video_audio_info;
/// No description provided for @player.
///
/// In en, this message translates to:
@ -2507,6 +2513,12 @@ abstract class AppLocalizations {
/// **'Advanced'**
String get advanced;
/// No description provided for @advanced_info.
///
/// In en, this message translates to:
/// **'mpv config'**
String get advanced_info;
/// No description provided for @use_native_http_client.
///
/// In en, this message translates to:
@ -2819,6 +2831,12 @@ abstract class AppLocalizations {
/// **'Hardware Decoder'**
String get hwdec;
/// No description provided for @track_library_navigate.
///
/// In en, this message translates to:
/// **'Go to existing local entry'**
String get track_library_navigate;
/// No description provided for @track_library_add.
///
/// In en, this message translates to:
@ -3004,6 +3022,114 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'{n} days'**
String n_days(Object n);
/// No description provided for @decoder.
///
/// In en, this message translates to:
/// **'Decoder'**
String get decoder;
/// No description provided for @decoder_info.
///
/// In en, this message translates to:
/// **'Hardware decoding, pixel format, debanding'**
String get decoder_info;
/// No description provided for @enable_gpu_next.
///
/// In en, this message translates to:
/// **'Enable gpu-next (Android only)'**
String get enable_gpu_next;
/// No description provided for @enable_gpu_next_info.
///
/// In en, this message translates to:
/// **'A new video rendering backend'**
String get enable_gpu_next_info;
/// No description provided for @debanding.
///
/// In en, this message translates to:
/// **'Debanding'**
String get debanding;
/// No description provided for @use_yuv420p.
///
/// In en, this message translates to:
/// **'Use YUV420P pixel format'**
String get use_yuv420p;
/// No description provided for @use_yuv420p_info.
///
/// In en, this message translates to:
/// **'May fix black screens on some video codecs, can also improve performance at the cost of quality'**
String get use_yuv420p_info;
/// No description provided for @audio_preferred_languages.
///
/// In en, this message translates to:
/// **'Preferred langauages'**
String get audio_preferred_languages;
/// No description provided for @audio_preferred_languages_info.
///
/// In en, this message translates to:
/// **'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.'**
String get audio_preferred_languages_info;
/// No description provided for @enable_audio_pitch_correction.
///
/// In en, this message translates to:
/// **'Enable audio pitch correction'**
String get enable_audio_pitch_correction;
/// No description provided for @enable_audio_pitch_correction_info.
///
/// In en, this message translates to:
/// **'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds'**
String get enable_audio_pitch_correction_info;
/// No description provided for @audio_channels.
///
/// In en, this message translates to:
/// **'Audio channels'**
String get audio_channels;
/// No description provided for @volume_boost_cap.
///
/// In en, this message translates to:
/// **'Volume boost cap'**
String get volume_boost_cap;
/// No description provided for @internal_player.
///
/// In en, this message translates to:
/// **'Internal player'**
String get internal_player;
/// No description provided for @internal_player_info.
///
/// In en, this message translates to:
/// **'Progress, controls, orientation'**
String get internal_player_info;
/// No description provided for @subtitle_delay_text.
///
/// In en, this message translates to:
/// **'Subtitle delay'**
String get subtitle_delay_text;
/// No description provided for @subtitle_delay.
///
/// In en, this message translates to:
/// **'Delay (ms)'**
String get subtitle_delay;
/// No description provided for @subtitle_speed.
///
/// In en, this message translates to:
/// **'Speed'**
String get subtitle_speed;
}
class _AppLocalizationsDelegate

View file

@ -1050,6 +1050,10 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get video_audio => 'الصوت';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'لاعب';
@ -1272,6 +1276,9 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get advanced => 'متقدم';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'استخدام عميل HTTP الأصلي';
@ -1450,6 +1457,9 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1548,4 +1558,61 @@ class AppLocalizationsAr extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1055,6 +1055,10 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get video_audio => 'Audio';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Player';
@ -1282,6 +1286,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get advanced => 'Erweitert';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Nativen HTTP-Client verwenden';
@ -1462,6 +1469,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Zur lokalen Bibliothek hinzufügen';
@ -1561,4 +1571,61 @@ class AppLocalizationsDe extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1049,6 +1049,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get video_audio => 'Audio';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Player';
@ -1272,6 +1276,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get advanced => 'Advanced';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Use native http client';
@ -1451,6 +1458,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1549,4 +1559,61 @@ class AppLocalizationsEn extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1059,6 +1059,10 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get video_audio => 'Audio';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Jugador';
@ -1286,6 +1290,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get advanced => 'Avanzado';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Utilizar cliente HTTP nativo';
@ -1468,6 +1475,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1566,6 +1576,63 @@ class AppLocalizationsEs extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}
/// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`).

View file

@ -1061,6 +1061,10 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get video_audio => 'Audio';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Lecteur';
@ -1288,6 +1292,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get advanced => 'Avancé';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Utiliser le client HTTP natif';
@ -1469,6 +1476,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1567,4 +1577,61 @@ class AppLocalizationsFr extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1055,6 +1055,10 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get video_audio => 'Audio';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Pemain';
@ -1279,6 +1283,9 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get advanced => 'Lanjutan';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Gunakan klien http asli';
@ -1457,6 +1464,9 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1555,4 +1565,61 @@ class AppLocalizationsId extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1058,6 +1058,10 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get video_audio => 'Audio';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Giocatore';
@ -1286,6 +1290,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get advanced => 'Avanzate';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Usa il client HTTP nativo';
@ -1466,6 +1473,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1564,4 +1574,61 @@ class AppLocalizationsIt extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1057,6 +1057,10 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get video_audio => 'Áudio';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Jogador';
@ -1283,6 +1287,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get advanced => 'Avançado';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Usar cliente HTTP nativo';
@ -1465,6 +1472,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1563,6 +1573,63 @@ class AppLocalizationsPt extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}
/// The translations for Portuguese, as used in Brazil (`pt_BR`).

View file

@ -1060,6 +1060,10 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get video_audio => 'Аудио';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Игрок';
@ -1286,6 +1290,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get advanced => 'Продвинутые';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Использовать нативный HTTP-клиент';
@ -1467,6 +1474,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1565,4 +1575,61 @@ class AppLocalizationsRu extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1049,6 +1049,10 @@ class AppLocalizationsTh extends AppLocalizations {
@override
String get video_audio => 'เสียง';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'ตัวเล่น';
@ -1273,6 +1277,9 @@ class AppLocalizationsTh extends AppLocalizations {
@override
String get advanced => 'ขั้นสูง';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'ใช้ไคลเอนต์ HTTP พื้นเมือง';
@ -1451,6 +1458,9 @@ class AppLocalizationsTh extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1549,4 +1559,61 @@ class AppLocalizationsTh extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1053,6 +1053,10 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get video_audio => 'Ses';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => 'Oyuncu';
@ -1278,6 +1282,9 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get advanced => 'Gelişmiş';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => 'Yerel http istemcisini kullan';
@ -1457,6 +1464,9 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1555,4 +1565,61 @@ class AppLocalizationsTr extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -1033,6 +1033,10 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get video_audio => '音频';
@override
String get video_audio_info =>
'Preferred languages, pitch correction, audio channels';
@override
String get player => '播放器';
@ -1250,6 +1254,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get advanced => '高级';
@override
String get advanced_info => 'mpv config';
@override
String get use_native_http_client => '使用本地 HTTP 客户端';
@ -1422,6 +1429,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get hwdec => 'Hardware Decoder';
@override
String get track_library_navigate => 'Go to existing local entry';
@override
String get track_library_add => 'Add to local library';
@ -1520,4 +1530,61 @@ class AppLocalizationsZh extends AppLocalizations {
String n_days(Object n) {
return '$n days';
}
@override
String get decoder => 'Decoder';
@override
String get decoder_info => 'Hardware decoding, pixel format, debanding';
@override
String get enable_gpu_next => 'Enable gpu-next (Android only)';
@override
String get enable_gpu_next_info => 'A new video rendering backend';
@override
String get debanding => 'Debanding';
@override
String get use_yuv420p => 'Use YUV420P pixel format';
@override
String get use_yuv420p_info =>
'May fix black screens on some video codecs, can also improve performance at the cost of quality';
@override
String get audio_preferred_languages => 'Preferred langauages';
@override
String get audio_preferred_languages_info =>
'Audio langauage(s) to be selected by default on a video with multiple audio streams, 2/3-letter languages codes (e.g.: en, de, fr) work. Multiple values can be delimited by a comma.';
@override
String get enable_audio_pitch_correction => 'Enable audio pitch correction';
@override
String get enable_audio_pitch_correction_info =>
'Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds';
@override
String get audio_channels => 'Audio channels';
@override
String get volume_boost_cap => 'Volume boost cap';
@override
String get internal_player => 'Internal player';
@override
String get internal_player_info => 'Progress, controls, orientation';
@override
String get subtitle_delay_text => 'Subtitle delay';
@override
String get subtitle_delay => 'Delay (ms)';
@override
String get subtitle_speed => 'Speed';
}

View file

@ -260,6 +260,22 @@ class Settings {
bool? useMpvConfig;
@enumerated
late DebandingType debandingType;
bool? enableGpuNext;
bool? useYUV420P;
String? audioPreferredLanguages;
bool? enableAudioPitchCorrection;
@enumerated
late AudioChannel audioChannels;
int? volumeBoostCap;
Settings({
this.id = 227,
this.updatedAt = 0,
@ -376,6 +392,13 @@ class Settings {
this.rpcShowTitle = true,
this.rpcShowCoverImage = true,
this.useMpvConfig = true,
this.debandingType = DebandingType.none,
this.enableGpuNext = false,
this.useYUV420P = false,
this.audioPreferredLanguages,
this.enableAudioPitchCorrection,
this.audioChannels = AudioChannel.autoSafe,
this.volumeBoostCap,
});
Settings.fromJson(Map<String, dynamic> json) {
@ -598,6 +621,15 @@ class Settings {
rpcShowTitle = json['rpcShowTitle'];
rpcShowCoverImage = json['rpcShowCoverImage'];
useMpvConfig = json['useMpvConfig'];
debandingType =
DebandingType.values[json['debandingType'] ?? DebandingType.none.index];
enableGpuNext = json['enableGpuNext'];
useYUV420P = json['useYUV420P'];
audioPreferredLanguages = json['audioPreferredLanguages'];
enableAudioPitchCorrection = json['enableAudioPitchCorrection'];
audioChannels = AudioChannel
.values[json['audioChannels'] ?? AudioChannel.autoSafe.index];
volumeBoostCap = json['volumeBoostCap'];
}
Map<String, dynamic> toJson() => {
@ -737,9 +769,30 @@ class Settings {
'rpcShowTitle': rpcShowTitle,
'rpcShowCoverImage': rpcShowCoverImage,
'useMpvConfig': useMpvConfig,
'debandingType': debandingType.index,
'enableGpuNext': enableGpuNext,
'useYUV420P': useYUV420P,
'audioPreferredLanguages': audioPreferredLanguages,
'enableAudioPitchCorrection': enableAudioPitchCorrection,
'audioChannels': audioChannels.index,
'volumeBoostCap': volumeBoostCap,
};
}
enum DebandingType { none, cpu, gpu }
enum AudioChannel {
auto(mpvName: "auto"),
autoSafe(mpvName: "auto-safe"),
mono(mpvName: "mono"),
stereo(mpvName: "stereo"),
reverseStereo(mpvName: "pan=[stereo|c0=c1|c1=c0]");
final String mpvName;
const AudioChannel({required this.mpvName});
}
enum SectionType { all, anime, manga }
enum DisplayType { compactGrid, comfortableGrid, coverOnlyGrid, list }

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@ import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/custom_button.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.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/widgets/aniskip_countdown_btn.dart';
@ -28,6 +29,8 @@ 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/custom_buttons_provider.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_audio_state_provider.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_decoder_state_provider.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
@ -191,11 +194,32 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
late final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
late final useLibass = ref.read(useLibassStateProvider);
late final useMpvConfig = ref.read(useMpvConfigStateProvider);
late final useGpuNext = ref.read(useGpuNextStateProvider);
late final debandingType = ref.read(debandingStateProvider);
late final useYUV420P = ref.read(useYUV420PStateProvider);
late final audioPreferredLang = ref.read(audioPreferredLangStateProvider);
late final enableAudioPitchCorrection = ref.read(
enableAudioPitchCorrectionStateProvider,
);
late final audioChannel = ref.read(audioChannelStateProvider);
late final volumeBoostCap = ref.read(volumeBoostCapStateProvider);
late final Player _player = Player(
configuration: PlayerConfiguration(
libass: useLibass,
config: true,
configDir: useMpvConfig ? widget.mpvDirectory?.path ?? "" : "",
options: {
if (debandingType == DebandingType.cpu) "vf": "gradfun=radius=12",
if (debandingType == DebandingType.gpu) "deband": "yes",
if (useYUV420P) "vf": "format=yuv420p",
"alang": audioPreferredLang,
if (enableAudioPitchCorrection) "audio-pitch-correction": "yes",
"volume-max": "${volumeBoostCap + 100}",
if (audioChannel != AudioChannel.reverseStereo)
"audio-channels": audioChannel.mpvName,
if (audioChannel == AudioChannel.reverseStereo)
"af": audioChannel.mpvName,
},
observeProperties: {
"user-data/aniyomi/show_text": generated.mpv_format.MPV_FORMAT_NODE,
"user-data/aniyomi/toggle_ui": generated.mpv_format.MPV_FORMAT_NODE,
@ -231,7 +255,14 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
late final hwdecMode = ref.read(hwdecModeStateProvider());
late final VideoController _controller = VideoController(
_player,
configuration: VideoControllerConfiguration(hwdec: hwdecMode),
configuration: VideoControllerConfiguration(
hwdec: hwdecMode,
vo: Platform.isAndroid
? useGpuNext
? "gpu-next"
: "gpu"
: "libmpv",
),
);
late final _streamController = ref.read(
animeStreamControllerProvider(episode: widget.episode).notifier,
@ -261,6 +292,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
final ValueNotifier<int?> _currentChapterMark = ValueNotifier(null);
final ValueNotifier<String> _selectedShader = ValueNotifier("");
final ValueNotifier<ActiveCustomButton?> _customButton = ValueNotifier(null);
final ValueNotifier<List<CustomButton>?> _customButtons = ValueNotifier(null);
late final ValueNotifier<_AniSkipPhase> _skipPhase = ValueNotifier(
_AniSkipPhase.none,
);
@ -270,6 +302,10 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
bool _hasEndingSkip = false;
bool _initSubtitleAndAudio = true;
bool _includeSubtitles = false;
int _subDelay = 0;
final _subDelayController = TextEditingController(text: "0");
double _subSpeed = 1;
final _subSpeedController = TextEditingController(text: "1");
int lastRpcTimestampUpdate = DateTime.now().millisecondsSinceEpoch;
late final StreamSubscription<Duration> _currentPositionSub;
@ -309,8 +345,10 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE) {
final prop = event.ref.data.cast<generated.mpv_event_property>();
final propName = prop.ref.name.cast<Utf8>().toDartString();
if (propName.startsWith("user-data/")) {
print("DEBUG 00: $propName - ${prop.ref.format}");
if (kDebugMode) {
if (propName.startsWith("user-data/")) {
print("DEBUG 00: $propName - ${prop.ref.format}");
}
}
if (propName.startsWith("user-data/") &&
prop.ref.format == generated.mpv_format.MPV_FORMAT_NODE) {
@ -469,9 +507,6 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
case "aniyomi/seek_by":
if (value.ref.format == generated.mpv_format.MPV_FORMAT_STRING) {
final text = value.ref.u.string.cast<Utf8>().toDartString();
final tt = await nativePlayer.getProperty(
"user-data/current-anime/intro-length",
);
if (text.isEmpty) break;
final data = int.parse(text.replaceAll("\"", ""));
final pos = _currentPosition.value.inSeconds + data;
@ -679,6 +714,7 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
"call_button_${primaryButton.id}_long",
]),
);
_customButtons.value = customButtons;
}
void pushToNewEpisode(BuildContext context, Chapter episode) {
@ -753,6 +789,47 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
}
}
void _onSubDelayChanged() {
final nativePlayer = (_player.platform as NativePlayer);
final delayMs = int.tryParse(_subDelayController.text);
if (delayMs != null) {
final namePtr = "sub-delay".toNativeUtf8();
final valuePtr = calloc<Double>(1)..value = delayMs / 1000;
nativePlayer.mpv.mpv_set_property(
nativePlayer.ctx,
namePtr.cast(),
generated.mpv_format.MPV_FORMAT_DOUBLE,
valuePtr.cast(),
);
malloc.free(namePtr);
malloc.free(valuePtr);
_subDelay = delayMs;
}
}
void _onSubSpeedChanged() {
final nativePlayer = (_player.platform as NativePlayer);
final speed = double.tryParse(_subSpeedController.text);
if (speed != null) {
final namePtr = "sub-speed".toNativeUtf8();
final valuePtr = calloc<Double>(1)
..value = speed < 0.1
? 0.1
: speed > 10
? 10
: speed;
nativePlayer.mpv.mpv_set_property(
nativePlayer.ctx,
namePtr.cast(),
generated.mpv_format.MPV_FORMAT_DOUBLE,
valuePtr.cast(),
);
malloc.free(namePtr);
malloc.free(valuePtr);
_subSpeed = speed;
}
}
@override
void initState() {
super.initState();
@ -798,6 +875,8 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
_initCustomButton();
discordRpc?.showChapterDetails(ref, widget.episode);
_currentPosition.addListener(_updateRpcTimestamp);
_subDelayController.addListener(_onSubDelayChanged);
_subSpeedController.addListener(_onSubSpeedChanged);
WidgetsBinding.instance.addObserver(this);
}
@ -886,6 +965,8 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
discordRpc?.showIdleText();
discordRpc?.showOriginalTimestamp();
_currentPosition.dispose();
_subDelayController.dispose();
_subSpeedController.dispose();
super.dispose();
}
@ -1117,6 +1198,91 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 12),
child: Column(
children: [
Row(
children: [
Text(context.l10n.subtitle_delay_text),
IconButton(
onPressed: () {
_subDelay = 0;
_subDelayController.value = TextEditingValue(
text: "$_subDelay",
);
_subSpeed = 1;
_subSpeedController.value = TextEditingValue(
text: _subSpeed.toStringAsFixed(2),
);
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 15),
Row(
children: [
IconButton(
onPressed: () {
_subDelay -= 50;
_subDelayController.value = TextEditingValue(
text: "$_subDelay",
);
},
icon: const Icon(Icons.remove_circle),
),
Expanded(
child: TextFormField(
controller: _subDelayController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
isDense: true,
label: Text(context.l10n.subtitle_delay),
),
),
),
IconButton(
onPressed: () {
_subDelay += 50;
_subDelayController.value = TextEditingValue(
text: "$_subDelay",
);
},
icon: const Icon(Icons.add_circle),
),
],
),
const SizedBox(height: 15),
Row(
children: [
IconButton(
onPressed: () {
_subSpeed -= 0.01;
_subSpeedController.value = TextEditingValue(
text: _subSpeed.toStringAsFixed(2),
);
},
icon: const Icon(Icons.remove_circle),
),
Expanded(
child: TextFormField(
controller: _subSpeedController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
isDense: true,
label: Text(context.l10n.subtitle_speed),
),
),
),
IconButton(
onPressed: () {
_subSpeed += 0.01;
_subSpeedController.value = TextEditingValue(
text: _subSpeed.toStringAsFixed(2),
);
},
icon: const Icon(Icons.add_circle),
),
],
),
const SizedBox(height: 15),
...videoSubtitleLast.toSet().toList().map((sub) {
final title =
sub.title ??
@ -1522,6 +1688,111 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
);
}
List<Widget> _buildMpvSettingsButton(BuildContext context) {
return [
PopupMenuButton<String>(
tooltip: 'Shaders',
icon: const Icon(Icons.high_quality, color: Colors.white),
itemBuilder: (context) =>
[
("Anime4K: Mode A (Fast)", "set_anime_a"),
("Anime4K: Mode B (Fast)", "set_anime_b"),
("Anime4K: Mode C (Fast)", "set_anime_c"),
("Anime4K: Mode A+A (Fast)", "set_anime_aa"),
("Anime4K: Mode B+B (Fast)", "set_anime_bb"),
("Anime4K: Mode C+A (Fast)", "set_anime_ca"),
("Anime4K: Mode A (HQ)", "set_anime_hq_a"),
("Anime4K: Mode B (HQ)", "set_anime_hq_b"),
("Anime4K: Mode C (HQ)", "set_anime_hq_c"),
("Anime4K: Mode A+A (HQ)", "set_anime_hq_aa"),
("Anime4K: Mode B+B (HQ)", "set_anime_hq_bb"),
("Anime4K: Mode C+A (HQ)", "set_anime_hq_ca"),
("AMD FSR", "set_fsr"),
("Luma Upscaling", "set_luma"),
("Qualcomm Snapdragon GSR", "set_snapdragon"),
("NVIDIA Image Scaling", "set_nvidia"),
("Clear GLSL shaders", "clear_anime"),
]
.map(
(mode) => PopupMenuItem<String>(
value: mode.$1,
child: Text(
mode.$1,
style: TextStyle(
fontWeight: _selectedShader.value == mode.$1
? FontWeight.w900
: FontWeight.normal,
),
),
onTap: () {
(_player.platform as NativePlayer).command([
"script-message",
mode.$2,
]);
},
),
)
.toList(),
),
PopupMenuButton<String>(
tooltip: 'Stats',
icon: const Icon(Icons.memory, color: Colors.white),
itemBuilder: (context) =>
[
("Stats Toggle", "stats/display-stats-toggle"),
("Stats Page 1", "stats/display-page-1"),
("Stats Page 2", "stats/display-page-2"),
("Stats Page 3", "stats/display-page-3"),
("Stats Page 4", "stats/display-page-4"),
("Stats Page 5", "stats/display-page-5"),
]
.map(
(mode) => PopupMenuItem<String>(
value: mode.$1,
child: Text(
mode.$1,
style: TextStyle(
fontWeight: _selectedShader.value == mode.$1
? FontWeight.w900
: FontWeight.normal,
),
),
onTap: () {
(_player.platform as NativePlayer).command([
"script-binding",
mode.$2,
]);
},
),
)
.toList(),
),
ValueListenableBuilder(
valueListenable: _customButtons,
builder: (context, value, child) => value != null
? PopupMenuButton<String>(
tooltip: context.l10n.custom_buttons,
icon: const Icon(Icons.terminal, color: Colors.white),
itemBuilder: (context) => value
.map(
(btn) => PopupMenuItem<String>(
value: btn.title!,
child: Text(btn.title!),
onTap: () {
(_player.platform as NativePlayer).command([
"script-message",
"call_button_${btn.id}",
]);
},
),
)
.toList(),
)
: Container(),
),
];
}
/// helper method for _mobileBottomButtonBar() and _desktopBottomButtonBar()
Widget _buildSettingsButtons(BuildContext context) {
final isFullscreen = ref.watch(fullscreenProvider);
@ -1532,85 +1803,7 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
onPressed: () => _videoSettingDraggableMenu(context),
icon: const Icon(Icons.video_settings, color: Colors.white),
),
if (useMpvConfig)
PopupMenuButton<String>(
tooltip: '', // Remove default tooltip "Show menu" for consistency
icon: const Icon(Icons.high_quality, color: Colors.white),
itemBuilder: (context) =>
[
("Anime4K: Mode A (Fast)", "set_anime_a"),
("Anime4K: Mode B (Fast)", "set_anime_b"),
("Anime4K: Mode C (Fast)", "set_anime_c"),
("Anime4K: Mode A+A (Fast)", "set_anime_aa"),
("Anime4K: Mode B+B (Fast)", "set_anime_bb"),
("Anime4K: Mode C+A (Fast)", "set_anime_ca"),
("Anime4K: Mode A (HQ)", "set_anime_hq_a"),
("Anime4K: Mode B (HQ)", "set_anime_hq_b"),
("Anime4K: Mode C (HQ)", "set_anime_hq_c"),
("Anime4K: Mode A+A (HQ)", "set_anime_hq_aa"),
("Anime4K: Mode B+B (HQ)", "set_anime_hq_bb"),
("Anime4K: Mode C+A (HQ)", "set_anime_hq_ca"),
("AMD FSR", "set_fsr"),
("Luma Upscaling", "set_luma"),
("Qualcomm Snapdragon GSR", "set_snapdragon"),
("NVIDIA Image Scaling", "set_nvidia"),
("Clear GLSL shaders", "clear_anime"),
]
.map(
(mode) => PopupMenuItem<String>(
value: mode.$1,
child: Text(
mode.$1,
style: TextStyle(
fontWeight: _selectedShader.value == mode.$1
? FontWeight.w900
: FontWeight.normal,
),
),
onTap: () {
(_player.platform as NativePlayer).command([
"script-message",
mode.$2,
]);
},
),
)
.toList(),
),
if (useMpvConfig)
PopupMenuButton<String>(
tooltip: '', // Remove default tooltip "Show menu" for consistency
icon: const Icon(Icons.terminal, color: Colors.white),
itemBuilder: (context) =>
[
("Stats Toggle", "stats/display-stats-toggle"),
("Stats Page 1", "stats/display-page-1"),
("Stats Page 2", "stats/display-page-2"),
("Stats Page 3", "stats/display-page-3"),
("Stats Page 4", "stats/display-page-4"),
("Stats Page 5", "stats/display-page-5"),
]
.map(
(mode) => PopupMenuItem<String>(
value: mode.$1,
child: Text(
mode.$1,
style: TextStyle(
fontWeight: _selectedShader.value == mode.$1
? FontWeight.w900
: FontWeight.normal,
),
),
onTap: () {
(_player.platform as NativePlayer).command([
"script-binding",
mode.$2,
]);
},
),
)
.toList(),
),
if (useMpvConfig) ..._buildMpvSettingsButton(context),
PopupMenuButton<double>(
tooltip: '', // Remove default tooltip "Show menu" for consistency
icon: const Icon(Icons.speed, color: Colors.white),

View file

@ -0,0 +1,138 @@
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:path/path.dart' as path;
import 'package:permission_handler/permission_handler.dart';
class PlayerAdvancedScreen extends ConsumerStatefulWidget {
const PlayerAdvancedScreen({super.key});
@override
ConsumerState<PlayerAdvancedScreen> createState() =>
_PlayerAdvancedScreenState();
}
class _PlayerAdvancedScreenState extends ConsumerState<PlayerAdvancedScreen> {
@override
Widget build(BuildContext context) {
final useMpvConfig = ref.watch(useMpvConfigStateProvider);
return Scaffold(
appBar: AppBar(title: Text(context.l10n.advanced)),
body: SingleChildScrollView(
child: Column(
children: [
SwitchListTile(
value: useMpvConfig,
title: Text(context.l10n.enable_mpv),
subtitle: Text(
context.l10n.mpv_info,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
onChanged: (value) async {
if (value && !(await _checkMpvConfig(context))) {
return;
}
ref.read(useMpvConfigStateProvider.notifier).set(value);
},
),
ListTile(
onTap: () {
_checkMpvConfig(context, redownload: true);
},
title: Text(context.l10n.mpv_redownload),
subtitle: Text(
context.l10n.mpv_redownload_info,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
],
),
),
);
}
Future<bool> _checkMpvConfig(
BuildContext context, {
bool redownload = false,
}) async {
var status = await Permission.storage.status;
if (!status.isGranted) {
await Permission.storage.request();
}
final provider = StorageProvider();
final dir = await provider.getMpvDirectory();
final mpvFile = File('${dir!.path}/mpv.conf');
final inputFile = File('${dir.path}/input.conf');
final filesMissing =
!(await mpvFile.exists()) && !(await inputFile.exists());
if ((redownload || filesMissing) && context.mounted) {
final res = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text(context.l10n.mpv_download),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(context.l10n.cancel),
),
const SizedBox(width: 15),
ElevatedButton(
onPressed: () async {
final bytes = await rootBundle.load(
"assets/mangayomi_mpv.zip",
);
final archive = ZipDecoder().decodeBytes(
bytes.buffer.asUint8List(),
);
String shadersDir = path.join(dir.path, 'shaders');
await Directory(shadersDir).create(recursive: true);
String scriptsDir = path.join(dir.path, 'scripts');
await Directory(scriptsDir).create(recursive: true);
for (final file in archive.files) {
if (file.name == "mpv.conf") {
await mpvFile.writeAsBytes(file.content);
} else if (file.name == "input.conf") {
await inputFile.writeAsBytes(file.content);
} else if (file.name.startsWith("shaders/") &&
file.name.endsWith(".glsl")) {
final shaderFile = File(
'$shadersDir/${file.name.split("/").last}',
);
await shaderFile.writeAsBytes(file.content);
} else if (file.name.startsWith("scripts/") &&
file.name.endsWith(".js")) {
final scriptFile = File(
'$scriptsDir/${file.name.split("/").last}',
);
await scriptFile.writeAsBytes(file.content);
}
}
if (context.mounted) {
Navigator.pop(context, "ok");
}
},
child: Text(context.l10n.download),
),
],
),
],
);
},
);
return res != null && res == "ok";
}
return context.mounted;
}
}

View file

@ -0,0 +1,236 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/l10n/generated/app_localizations.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/settings/player/custom_button_screen.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_audio_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class PlayerAudioScreen extends ConsumerStatefulWidget {
const PlayerAudioScreen({super.key});
@override
ConsumerState<PlayerAudioScreen> createState() => _PlayerAudioScreenState();
}
class _PlayerAudioScreenState extends ConsumerState<PlayerAudioScreen> {
@override
Widget build(BuildContext context) {
final audioPreferredLang = ref.watch(audioPreferredLangStateProvider);
final enableAudioPitchCorrection = ref.watch(
enableAudioPitchCorrectionStateProvider,
);
final audioChannel = ref.watch(audioChannelStateProvider);
final volumeBoostCap = ref.watch(volumeBoostCapStateProvider);
return Scaffold(
appBar: AppBar(title: Text(context.l10n.video_audio)),
body: SingleChildScrollView(
child: Column(
children: [
ListTile(
onTap: () => _showEditController(),
title: Text(context.l10n.audio_preferred_languages),
subtitle: Text(
audioPreferredLang,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
SwitchListTile(
value: enableAudioPitchCorrection,
title: Text(context.l10n.enable_audio_pitch_correction),
subtitle: Text(
context.l10n.enable_audio_pitch_correction_info,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
onChanged: (value) {
ref
.read(enableAudioPitchCorrectionStateProvider.notifier)
.set(value);
},
),
ListTile(
onTap: () {
final values = [
(AudioChannel.auto, "Auto"),
(AudioChannel.autoSafe, "Auto-safe"),
(AudioChannel.mono, "Mono"),
(AudioChannel.stereo, "Stereo"),
(AudioChannel.reverseStereo, "Reverse stereo"),
];
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.audio_channels),
content: SizedBox(
width: context.width(0.8),
child: SuperListView.builder(
shrinkWrap: true,
itemCount: values.length,
itemBuilder: (context, index) {
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: values[index].$1,
groupValue: audioChannel,
onChanged: (value) {
ref
.read(audioChannelStateProvider.notifier)
.set(value!);
Navigator.pop(context);
},
title: Row(children: [Text(values[index].$2)]),
);
},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
context.l10n.cancel,
style: TextStyle(color: context.primaryColor),
),
),
],
),
],
);
},
);
},
title: Text(context.l10n.audio_channels),
subtitle: Text(
audioChannel.name,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.l10n.volume_boost_cap),
Text(
"$volumeBoostCap",
style: TextStyle(
fontSize: 11,
color: context.secondaryColor,
),
),
const SizedBox(height: 20),
SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: const RoundSliderOverlayShape(
overlayRadius: 5.0,
),
),
child: Slider.adaptive(
min: 0,
max: 200,
value: volumeBoostCap.toDouble(),
onChanged: (value) {
HapticFeedback.vibrate();
ref
.read(volumeBoostCapStateProvider.notifier)
.set(value.toInt());
},
onChangeEnd: (value) {
ref
.read(volumeBoostCapStateProvider.notifier)
.set(value.toInt());
},
),
),
],
),
),
],
),
),
);
}
void _showEditController() {
final audioPreferredLang = ref.read(audioPreferredLangStateProvider);
final langCodes = AppLocalizations.supportedLocales
.map((e) => e.languageCode)
.toList();
bool isLangCodeError = false;
final textController = TextEditingController(text: audioPreferredLang);
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Column(
children: [
Text(context.l10n.audio_preferred_languages),
Text(
context.l10n.audio_preferred_languages_info,
style: TextStyle(
fontSize: 11,
color: context.secondaryColor,
),
),
],
),
content: SizedBox(
width: context.width(0.8),
child: CustomTextFormField(
controller: textController,
context: context,
isMissing: isLangCodeError,
val: (text) => setState(() {
isLangCodeError = text
.split(",")
.any((e) => !langCodes.contains(e));
}),
missing: (_) {},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
context.l10n.cancel,
style: TextStyle(color: context.primaryColor),
),
),
TextButton(
onPressed: () {
ref
.read(audioPreferredLangStateProvider.notifier)
.set(textController.text);
Navigator.pop(context);
},
child: Text(
context.l10n.ok,
style: TextStyle(color: context.primaryColor),
),
),
],
),
],
);
},
);
},
);
}
}

View file

@ -0,0 +1,195 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_decoder_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class PlayerDecoderScreen extends ConsumerStatefulWidget {
const PlayerDecoderScreen({super.key});
@override
ConsumerState<PlayerDecoderScreen> createState() =>
_PlayerDecoderScreenState();
}
class _PlayerDecoderScreenState extends ConsumerState<PlayerDecoderScreen> {
@override
Widget build(BuildContext context) {
final hwdecMode = ref.watch(hwdecModeStateProvider(rawValue: true));
final useGpuNext = ref.watch(useGpuNextStateProvider);
final debandingType = ref.watch(debandingStateProvider);
final useYUV420P = ref.watch(useYUV420PStateProvider);
return Scaffold(
appBar: AppBar(title: Text(context.l10n.decoder)),
body: SingleChildScrollView(
child: Column(
children: [
ListTile(
onTap: () {
final values = [
("no", ""),
("auto", ""),
("d3d11va", "(Windows 8+)"),
("d3d11va-copy", "(Windows 8+)"),
("videotoolbox", "(iOS 9.0+)"),
("videotoolbox-copy", "(iOS 9.0+)"),
("nvdec", "(CUDA)"),
("nvdec-copy", "(CUDA)"),
("mediacodec", "- HW (Android)"),
("mediacodec-copy", "- HW+ (Android)"),
("crystalhd", ""),
];
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.hwdec),
content: SizedBox(
width: context.width(0.8),
child: SuperListView.builder(
shrinkWrap: true,
itemCount: values.length,
itemBuilder: (context, index) {
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: values[index].$1,
groupValue: hwdecMode,
onChanged: (value) {
ref
.read(
hwdecModeStateProvider(
rawValue: true,
).notifier,
)
.set(value!);
Navigator.pop(context);
},
title: Row(
children: [
Text(
"${values[index].$1} ${values[index].$2}",
),
],
),
);
},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
context.l10n.cancel,
style: TextStyle(color: context.primaryColor),
),
),
],
),
],
);
},
);
},
title: Text(context.l10n.hwdec),
subtitle: Text(
hwdecMode,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
SwitchListTile(
value: useGpuNext,
title: Text(context.l10n.enable_gpu_next),
subtitle: Text(
context.l10n.enable_gpu_next_info,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
onChanged: (value) {
ref.read(useGpuNextStateProvider.notifier).set(value);
},
),
ListTile(
onTap: () {
final values = [
(DebandingType.none, "None"),
(DebandingType.cpu, "CPU"),
(DebandingType.gpu, "GPU"),
];
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.debanding),
content: SizedBox(
width: context.width(0.8),
child: SuperListView.builder(
shrinkWrap: true,
itemCount: values.length,
itemBuilder: (context, index) {
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: values[index].$1,
groupValue: debandingType,
onChanged: (value) {
ref
.read(debandingStateProvider.notifier)
.set(value!);
Navigator.pop(context);
},
title: Row(children: [Text(values[index].$2)]),
);
},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
context.l10n.cancel,
style: TextStyle(color: context.primaryColor),
),
),
],
),
],
);
},
);
},
title: Text(context.l10n.debanding),
subtitle: Text(
debandingType.name,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
SwitchListTile(
value: useYUV420P,
title: Text(context.l10n.use_yuv420p),
subtitle: Text(
context.l10n.use_yuv420p_info,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
onChanged: (value) {
ref.read(useYUV420PStateProvider.notifier).set(value);
},
),
],
),
),
);
}
}

View file

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
class PlayerOverviewScreen extends StatelessWidget {
const PlayerOverviewScreen({super.key});
@override
Widget build(BuildContext context) {
final l10n = l10nLocalizations(context);
return Scaffold(
appBar: AppBar(title: Text(l10n!.player)),
body: SingleChildScrollView(
child: Column(
children: [
ListTileWidget(
title: l10n.internal_player,
subtitle: l10n.internal_player_info,
icon: Icons.play_circle_outline_outlined,
onTap: () => context.push('/playerMode'),
),
ListTileWidget(
title: l10n.decoder,
subtitle: l10n.decoder_info,
icon: Icons.memory_outlined,
onTap: () => context.push('/playerDecoderScreen'),
),
ListTileWidget(
title: l10n.video_audio,
subtitle: l10n.video_audio_info,
icon: Icons.audiotrack_outlined,
onTap: () => context.push('/playerAudioScreen'),
),
ListTileWidget(
title: l10n.custom_buttons,
subtitle: l10n.custom_buttons_info,
icon: Icons.terminal_outlined,
onTap: () => context.push('/customButtonScreen'),
),
ListTileWidget(
title: l10n.advanced,
subtitle: l10n.advanced_info,
icon: Icons.code_outlined,
onTap: () => context.push('/playerAdvancedScreen'),
),
],
),
),
);
}
}

View file

@ -1,20 +1,10 @@
import 'dart:async';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/language.dart';
import 'package:numberpicker/numberpicker.dart';
import 'package:path/path.dart' as path;
import 'package:permission_handler/permission_handler.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:mangayomi/l10n/generated/app_localizations.dart';
@ -41,12 +31,10 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
final enableAutoSkip = ref.watch(enableAutoSkipStateProvider);
final aniSkipTimeoutLength = ref.watch(aniSkipTimeoutLengthStateProvider);
final useLibass = ref.watch(useLibassStateProvider);
final useMpvConfig = ref.watch(useMpvConfigStateProvider);
final hwdecMode = ref.watch(hwdecModeStateProvider(rawValue: true));
final fullScreenPlayer = ref.watch(fullScreenPlayerStateProvider);
return Scaffold(
appBar: AppBar(title: Text(context.l10n.player)),
appBar: AppBar(title: Text(context.l10n.internal_player)),
body: SingleChildScrollView(
child: Column(
children: [
@ -359,23 +347,6 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
ListTile(
title: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(
Icons.info_outline_rounded,
color: context.secondaryColor,
),
],
),
),
subtitle: Text(
context.l10n.aniskip_requires_info,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
SwitchListTile(
value: useLibass,
title: Text(context.l10n.use_libass),
@ -396,6 +367,26 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
onExpansionChanged: (value) =>
ref.read(enableAniSkipStateProvider.notifier).set(value),
children: [
ListTile(
title: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(
Icons.info_outline_rounded,
color: context.secondaryColor,
),
],
),
),
subtitle: Text(
context.l10n.aniskip_requires_info,
style: TextStyle(
fontSize: 11,
color: context.secondaryColor,
),
),
),
SwitchListTile(
value: enableAutoSkip,
title: Text(context.l10n.enable_auto_skip),
@ -473,38 +464,6 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
),
],
),
SwitchListTile(
value: useMpvConfig,
title: Text(context.l10n.enable_mpv),
subtitle: Text(
context.l10n.mpv_info,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
onChanged: (value) async {
if (value && !(await _checkMpvConfig(context))) {
return;
}
ref.read(useMpvConfigStateProvider.notifier).set(value);
},
),
ListTile(
onTap: () {
_checkMpvConfig(context, redownload: true);
},
title: Text(context.l10n.mpv_redownload),
subtitle: Text(
context.l10n.mpv_redownload_info,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
ListTileWidget(
onTap: () {
context.push("/customButtonScreen");
},
icon: Icons.terminal,
title: context.l10n.custom_buttons,
subtitle: context.l10n.custom_buttons_info,
),
SwitchListTile(
value: fullScreenPlayer,
title: Text(context.l10n.full_screen_player),
@ -516,164 +475,9 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
ref.read(fullScreenPlayerStateProvider.notifier).set(value);
},
),
ListTile(
onTap: () {
final values = [
("no", ""),
("auto", ""),
("d3d11va", "(Windows 8+)"),
("d3d11va-copy", "(Windows 8+)"),
("videotoolbox", "(iOS 9.0+)"),
("videotoolbox-copy", "(iOS 9.0+)"),
("nvdec", "(CUDA)"),
("nvdec-copy", "(CUDA)"),
("mediacodec", "- HW (Android)"),
("mediacodec-copy", "- HW+ (Android)"),
("crystalhd", ""),
];
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.hwdec),
content: SizedBox(
width: context.width(0.8),
child: SuperListView.builder(
shrinkWrap: true,
itemCount: values.length,
itemBuilder: (context, index) {
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: values[index].$1,
groupValue: hwdecMode,
onChanged: (value) {
ref
.read(
hwdecModeStateProvider(
rawValue: true,
).notifier,
)
.set(value!);
Navigator.pop(context);
},
title: Row(
children: [
Text(
"${values[index].$1} ${values[index].$2}",
),
],
),
);
},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
context.l10n.cancel,
style: TextStyle(color: context.primaryColor),
),
),
],
),
],
);
},
);
},
title: Text(context.l10n.hwdec),
subtitle: Text(
hwdecMode,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
],
),
),
);
}
Future<bool> _checkMpvConfig(
BuildContext context, {
bool redownload = false,
}) async {
var status = await Permission.storage.status;
if (!status.isGranted) {
await Permission.storage.request();
}
final provider = StorageProvider();
final dir = await provider.getMpvDirectory();
final mpvFile = File('${dir!.path}/mpv.conf');
final inputFile = File('${dir.path}/input.conf');
final filesMissing =
!(await mpvFile.exists()) && !(await inputFile.exists());
if ((redownload || filesMissing) && context.mounted) {
final res = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text(context.l10n.mpv_download),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(context.l10n.cancel),
),
const SizedBox(width: 15),
ElevatedButton(
onPressed: () async {
final bytes = await rootBundle.load(
"assets/mangayomi_mpv.zip",
);
final archive = ZipDecoder().decodeBytes(
bytes.buffer.asUint8List(),
);
String shadersDir = path.join(dir.path, 'shaders');
await Directory(shadersDir).create(recursive: true);
String scriptsDir = path.join(dir.path, 'scripts');
await Directory(scriptsDir).create(recursive: true);
for (final file in archive.files) {
if (file.name == "mpv.conf") {
await mpvFile.writeAsBytes(file.content);
} else if (file.name == "input.conf") {
await inputFile.writeAsBytes(file.content);
} else if (file.name.startsWith("shaders/") &&
file.name.endsWith(".glsl")) {
final shaderFile = File(
'$shadersDir/${file.name.split("/").last}',
);
await shaderFile.writeAsBytes(file.content);
} else if (file.name.startsWith("scripts/") &&
file.name.endsWith(".js")) {
final scriptFile = File(
'$scriptsDir/${file.name.split("/").last}',
);
await scriptFile.writeAsBytes(file.content);
}
}
if (context.mounted) {
Navigator.pop(context, "ok");
}
},
child: Text(context.l10n.download),
),
],
),
],
);
},
);
return res != null && res == "ok";
}
return context.mounted;
}
}

View file

@ -0,0 +1,86 @@
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'player_audio_state_provider.g.dart';
@riverpod
class AudioPreferredLangState extends _$AudioPreferredLangState {
@override
String build() {
return isar.settings.getSync(227)!.audioPreferredLanguages ?? "";
}
void set(String value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..audioPreferredLanguages = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}
@riverpod
class EnableAudioPitchCorrectionState
extends _$EnableAudioPitchCorrectionState {
@override
bool build() {
return isar.settings.getSync(227)!.enableAudioPitchCorrection ?? true;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..enableAudioPitchCorrection = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}
@riverpod
class AudioChannelState extends _$AudioChannelState {
@override
AudioChannel build() {
return isar.settings.getSync(227)!.audioChannels;
}
void set(AudioChannel value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..audioChannels = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}
@riverpod
class VolumeBoostCapState extends _$VolumeBoostCapState {
@override
int build() {
return isar.settings.getSync(227)!.volumeBoostCap ?? 30;
}
void set(int value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..volumeBoostCap = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}

View file

@ -0,0 +1,77 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'player_audio_state_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$audioPreferredLangStateHash() =>
r'9d70ec2677efb51b8e0c174b55114865853f12ea';
/// See also [AudioPreferredLangState].
@ProviderFor(AudioPreferredLangState)
final audioPreferredLangStateProvider =
AutoDisposeNotifierProvider<AudioPreferredLangState, String>.internal(
AudioPreferredLangState.new,
name: r'audioPreferredLangStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$audioPreferredLangStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AudioPreferredLangState = AutoDisposeNotifier<String>;
String _$enableAudioPitchCorrectionStateHash() =>
r'6614f4b04ff8fe8ef57c9a6f160646d3d25e2f4d';
/// See also [EnableAudioPitchCorrectionState].
@ProviderFor(EnableAudioPitchCorrectionState)
final enableAudioPitchCorrectionStateProvider =
AutoDisposeNotifierProvider<EnableAudioPitchCorrectionState, bool>.internal(
EnableAudioPitchCorrectionState.new,
name: r'enableAudioPitchCorrectionStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$enableAudioPitchCorrectionStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$EnableAudioPitchCorrectionState = AutoDisposeNotifier<bool>;
String _$audioChannelStateHash() => r'e71ffa85c37d545fb7b22e9539241b4926a2d384';
/// See also [AudioChannelState].
@ProviderFor(AudioChannelState)
final audioChannelStateProvider =
AutoDisposeNotifierProvider<AudioChannelState, AudioChannel>.internal(
AudioChannelState.new,
name: r'audioChannelStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$audioChannelStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AudioChannelState = AutoDisposeNotifier<AudioChannel>;
String _$volumeBoostCapStateHash() =>
r'b0f5ad3bbb0e1a798ce229572b363465ad606a06';
/// See also [VolumeBoostCapState].
@ProviderFor(VolumeBoostCapState)
final volumeBoostCapStateProvider =
AutoDisposeNotifierProvider<VolumeBoostCapState, int>.internal(
VolumeBoostCapState.new,
name: r'volumeBoostCapStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$volumeBoostCapStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$VolumeBoostCapState = AutoDisposeNotifier<int>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -0,0 +1,110 @@
import 'dart:io';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'player_decoder_state_provider.g.dart';
final hwdecs = {
"no": ["all"],
"auto": ["all"],
"d3d11va": ["windows"],
"d3d11va-copy": ["windows"],
"videotoolbox": ["ios"],
"videotoolbox-copy": ["ios"],
"nvdec": ["all"],
"nvdec-copy": ["all"],
"mediacodec": ["android"],
"mediacodec-copy": ["android"],
"crystalhd": ["all"],
};
@riverpod
class HwdecModeState extends _$HwdecModeState {
@override
String build({bool rawValue = false}) {
final hwdecMode = isar.settings.getSync(227)!.hwdecMode ?? "auto";
if (rawValue) {
return hwdecMode;
}
final hwdecSupport = hwdecs[hwdecMode] ?? [];
if (!hwdecSupport.contains("all") &&
!hwdecSupport.contains(Platform.operatingSystem)) {
return Platform.isAndroid ? "auto-safe" : "auto";
}
return hwdecMode;
}
void set(String value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..hwdecMode = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}
@riverpod
class DebandingState extends _$DebandingState {
@override
DebandingType build() {
return isar.settings.getSync(227)!.debandingType;
}
void set(DebandingType value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..debandingType = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}
@riverpod
class UseGpuNextState extends _$UseGpuNextState {
@override
bool build() {
return isar.settings.getSync(227)!.enableGpuNext ?? false;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..enableGpuNext = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}
@riverpod
class UseYUV420PState extends _$UseYUV420PState {
@override
bool build() {
return isar.settings.getSync(227)!.useYUV420P ?? false;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..useYUV420P = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}

View file

@ -0,0 +1,223 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'player_decoder_state_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$hwdecModeStateHash() => r'8186e3c5f3db0e952f629d56b2e580e546aed65e';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$HwdecModeState extends BuildlessAutoDisposeNotifier<String> {
late final bool rawValue;
String build({
bool rawValue = false,
});
}
/// See also [HwdecModeState].
@ProviderFor(HwdecModeState)
const hwdecModeStateProvider = HwdecModeStateFamily();
/// See also [HwdecModeState].
class HwdecModeStateFamily extends Family<String> {
/// See also [HwdecModeState].
const HwdecModeStateFamily();
/// See also [HwdecModeState].
HwdecModeStateProvider call({
bool rawValue = false,
}) {
return HwdecModeStateProvider(
rawValue: rawValue,
);
}
@override
HwdecModeStateProvider getProviderOverride(
covariant HwdecModeStateProvider provider,
) {
return call(
rawValue: provider.rawValue,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'hwdecModeStateProvider';
}
/// See also [HwdecModeState].
class HwdecModeStateProvider
extends AutoDisposeNotifierProviderImpl<HwdecModeState, String> {
/// See also [HwdecModeState].
HwdecModeStateProvider({
bool rawValue = false,
}) : this._internal(
() => HwdecModeState()..rawValue = rawValue,
from: hwdecModeStateProvider,
name: r'hwdecModeStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$hwdecModeStateHash,
dependencies: HwdecModeStateFamily._dependencies,
allTransitiveDependencies:
HwdecModeStateFamily._allTransitiveDependencies,
rawValue: rawValue,
);
HwdecModeStateProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.rawValue,
}) : super.internal();
final bool rawValue;
@override
String runNotifierBuild(
covariant HwdecModeState notifier,
) {
return notifier.build(
rawValue: rawValue,
);
}
@override
Override overrideWith(HwdecModeState Function() create) {
return ProviderOverride(
origin: this,
override: HwdecModeStateProvider._internal(
() => create()..rawValue = rawValue,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
rawValue: rawValue,
),
);
}
@override
AutoDisposeNotifierProviderElement<HwdecModeState, String> createElement() {
return _HwdecModeStateProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is HwdecModeStateProvider && other.rawValue == rawValue;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, rawValue.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin HwdecModeStateRef on AutoDisposeNotifierProviderRef<String> {
/// The parameter `rawValue` of this provider.
bool get rawValue;
}
class _HwdecModeStateProviderElement
extends AutoDisposeNotifierProviderElement<HwdecModeState, String>
with HwdecModeStateRef {
_HwdecModeStateProviderElement(super.provider);
@override
bool get rawValue => (origin as HwdecModeStateProvider).rawValue;
}
String _$debandingStateHash() => r'b93e2fc826d98cc8bce1aab9a92900353e4d3958';
/// See also [DebandingState].
@ProviderFor(DebandingState)
final debandingStateProvider =
AutoDisposeNotifierProvider<DebandingState, DebandingType>.internal(
DebandingState.new,
name: r'debandingStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$debandingStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$DebandingState = AutoDisposeNotifier<DebandingType>;
String _$useGpuNextStateHash() => r'cfc109cd7db66e359e9523102a84aa8cf37bf243';
/// See also [UseGpuNextState].
@ProviderFor(UseGpuNextState)
final useGpuNextStateProvider =
AutoDisposeNotifierProvider<UseGpuNextState, bool>.internal(
UseGpuNextState.new,
name: r'useGpuNextStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$useGpuNextStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$UseGpuNextState = AutoDisposeNotifier<bool>;
String _$useYUV420PStateHash() => r'c600001eff34b2b8df31ba604413b8b20edc3044';
/// See also [UseYUV420PState].
@ProviderFor(UseYUV420PState)
final useYUV420PStateProvider =
AutoDisposeNotifierProvider<UseYUV420PState, bool>.internal(
UseYUV420PState.new,
name: r'useYUV420PStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$useYUV420PStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$UseYUV420PState = AutoDisposeNotifier<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -233,46 +233,3 @@ class UseMpvConfigState extends _$UseMpvConfigState {
);
}
}
final hwdecs = {
"no": ["all"],
"auto": ["all"],
"d3d11va": ["windows"],
"d3d11va-copy": ["windows"],
"videotoolbox": ["ios"],
"videotoolbox-copy": ["ios"],
"nvdec": ["all"],
"nvdec-copy": ["all"],
"mediacodec": ["android"],
"mediacodec-copy": ["android"],
"crystalhd": ["all"],
};
@riverpod
class HwdecModeState extends _$HwdecModeState {
@override
String build({bool rawValue = false}) {
final hwdecMode = isar.settings.getSync(227)!.hwdecMode ?? "auto";
if (rawValue) {
return hwdecMode;
}
final hwdecSupport = hwdecs[hwdecMode] ?? [];
if (!hwdecSupport.contains("all") &&
!hwdecSupport.contains(Platform.operatingSystem)) {
return Platform.isAndroid ? "auto-safe" : "auto";
}
return hwdecMode;
}
void set(String value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!
..hwdecMode = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}

View file

@ -191,169 +191,5 @@ final useMpvConfigStateProvider =
);
typedef _$UseMpvConfigState = AutoDisposeNotifier<bool>;
String _$hwdecModeStateHash() => r'8186e3c5f3db0e952f629d56b2e580e546aed65e';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$HwdecModeState extends BuildlessAutoDisposeNotifier<String> {
late final bool rawValue;
String build({
bool rawValue = false,
});
}
/// See also [HwdecModeState].
@ProviderFor(HwdecModeState)
const hwdecModeStateProvider = HwdecModeStateFamily();
/// See also [HwdecModeState].
class HwdecModeStateFamily extends Family<String> {
/// See also [HwdecModeState].
const HwdecModeStateFamily();
/// See also [HwdecModeState].
HwdecModeStateProvider call({
bool rawValue = false,
}) {
return HwdecModeStateProvider(
rawValue: rawValue,
);
}
@override
HwdecModeStateProvider getProviderOverride(
covariant HwdecModeStateProvider provider,
) {
return call(
rawValue: provider.rawValue,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'hwdecModeStateProvider';
}
/// See also [HwdecModeState].
class HwdecModeStateProvider
extends AutoDisposeNotifierProviderImpl<HwdecModeState, String> {
/// See also [HwdecModeState].
HwdecModeStateProvider({
bool rawValue = false,
}) : this._internal(
() => HwdecModeState()..rawValue = rawValue,
from: hwdecModeStateProvider,
name: r'hwdecModeStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$hwdecModeStateHash,
dependencies: HwdecModeStateFamily._dependencies,
allTransitiveDependencies:
HwdecModeStateFamily._allTransitiveDependencies,
rawValue: rawValue,
);
HwdecModeStateProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.rawValue,
}) : super.internal();
final bool rawValue;
@override
String runNotifierBuild(
covariant HwdecModeState notifier,
) {
return notifier.build(
rawValue: rawValue,
);
}
@override
Override overrideWith(HwdecModeState Function() create) {
return ProviderOverride(
origin: this,
override: HwdecModeStateProvider._internal(
() => create()..rawValue = rawValue,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
rawValue: rawValue,
),
);
}
@override
AutoDisposeNotifierProviderElement<HwdecModeState, String> createElement() {
return _HwdecModeStateProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is HwdecModeStateProvider && other.rawValue == rawValue;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, rawValue.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin HwdecModeStateRef on AutoDisposeNotifierProviderRef<String> {
/// The parameter `rawValue` of this provider.
bool get rawValue;
}
class _HwdecModeStateProviderElement
extends AutoDisposeNotifierProviderElement<HwdecModeState, String>
with HwdecModeStateRef {
_HwdecModeStateProviderElement(super.provider);
@override
bool get rawValue => (origin as HwdecModeStateProvider).rawValue;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -21,52 +21,43 @@ class SettingsScreen extends StatelessWidget {
),
ListTileWidget(
title: l10n.appearance,
subtitle: l10n.appearance_subtitle,
icon: Icons.color_lens_rounded,
onTap: () => context.push('/appearance'),
),
ListTileWidget(
title: l10n.reader,
subtitle: l10n.reader_subtitle,
icon: Icons.chrome_reader_mode_rounded,
onTap: () => context.push('/readerMode'),
),
ListTileWidget(
title: l10n.player,
subtitle: l10n.reader_subtitle,
icon: Icons.play_circle_outline_outlined,
onTap: () => context.push('/playerMode'),
onTap: () => context.push('/playerOverview'),
),
ListTileWidget(
title: l10n.downloads,
subtitle: l10n.downloads_subtitle,
icon: Icons.download_outlined,
onTap: () => context.push('/downloads'),
),
ListTileWidget(
title: l10n.tracking,
subtitle: "",
icon: Icons.sync_outlined,
onTap: () => context.push('/track'),
),
ListTileWidget(
title: l10n.syncing,
subtitle: l10n.syncing_subtitle,
icon: Icons.cloud_sync_outlined,
onTap: () => context.push('/sync'),
),
ListTileWidget(
title: l10n.browse,
subtitle: l10n.browse_subtitle,
icon: Icons.explore_rounded,
onTap: () => context.push('/browseS'),
),
ListTileWidget(
onTap: () {
context.push('/about');
},
icon: Icons.info_outline,
title: l10n.about,
icon: Icons.info_outline,
onTap: () => context.push('/about'),
),
],
),

View file

@ -15,11 +15,13 @@ import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
class TrackerItemCard extends StatelessWidget {
final TrackSearch track;
final ItemType itemType;
final int? mangaId;
const TrackerItemCard({
super.key,
required this.track,
required this.itemType,
this.mangaId,
});
@override
@ -140,6 +142,12 @@ class TrackerItemCard extends StatelessWidget {
initExpanded: false,
onChanged: (value) {},
),
if (mangaId != null)
TextButton.icon(
onPressed: () => _pushLocalLibrary(context),
label: Text(l10n.track_library_navigate),
icon: Icon(Icons.north_east),
),
TextButton.icon(
onPressed: () => _pushMigrationScreen(context),
label: Text(l10n.track_library_add),
@ -182,6 +190,12 @@ class TrackerItemCard extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
if (mangaId != null)
TextButton.icon(
onPressed: () => _pushLocalLibrary(context),
label: Text(l10n.track_library_navigate),
icon: Icon(Icons.north_east),
),
TextButton.icon(
onPressed: () => _pushMigrationScreen(context),
label: Text(l10n.track_library_add),
@ -250,6 +264,10 @@ class TrackerItemCard extends StatelessWidget {
);
}
void _pushLocalLibrary(BuildContext context) {
context.push('/manga-reader/detail', extra: mangaId);
}
void _pushMigrationScreen(BuildContext context) {
context.push(
"/migrate/tracker",

View file

@ -33,18 +33,18 @@ class _TrackerLibraryImageCardState
Widget build(BuildContext context) {
super.build(context);
final trackData = widget.track;
return GestureDetector(
onTap: () => _showCard(context),
child: StreamBuilder(
stream: isar.tracks
.filter()
.mangaIdIsNotNull()
.mediaIdEqualTo(trackData.mediaId)
.itemTypeEqualTo(widget.itemType)
.watch(fireImmediately: true),
builder: (context, snapshot) {
final hasData = snapshot.hasData && snapshot.data!.isNotEmpty;
return Padding(
return StreamBuilder(
stream: isar.tracks
.filter()
.mangaIdIsNotNull()
.mediaIdEqualTo(trackData.mediaId)
.itemTypeEqualTo(widget.itemType)
.watch(fireImmediately: true),
builder: (context, snapshot) {
final hasData = snapshot.hasData && snapshot.data!.isNotEmpty;
return GestureDetector(
onTap: () => _showCard(context, snapshot.data?.firstOrNull?.mangaId),
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Stack(
children: [
@ -129,17 +129,20 @@ class _TrackerLibraryImageCardState
),
],
),
);
},
),
),
);
},
);
}
void _showCard(BuildContext context) {
void _showCard(BuildContext context, int? mangaId) {
showDialog(
context: context,
builder: (context) =>
TrackerItemCard(track: widget.track, itemType: widget.itemType),
builder: (context) => TrackerItemCard(
track: widget.track,
itemType: widget.itemType,
mangaId: mangaId,
),
);
}

View file

@ -17,6 +17,10 @@ import 'package:mangayomi/modules/more/data_and_storage/data_and_storage.dart';
import 'package:mangayomi/modules/more/settings/appearance/custom_navigation_settings.dart';
import 'package:mangayomi/modules/more/settings/browse/source_repositories.dart';
import 'package:mangayomi/modules/more/settings/player/custom_button_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_advanced_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_audio_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_decoder_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_overview_screen.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/modules/more/statistics/statistics_screen.dart';
import 'package:mangayomi/modules/novel/novel_reader_view.dart';
@ -204,6 +208,7 @@ class RouterNotifier extends ChangeNotifier {
name: "trackingDetail",
builder: (trackerPref) => TrackingDetail(trackerPref: trackerPref),
),
_genericRoute(name: "playerOverview", child: const PlayerOverviewScreen()),
_genericRoute(name: "playerMode", child: const PlayerScreen()),
_genericRoute<int>(
name: "codeEditor",
@ -219,6 +224,15 @@ class RouterNotifier extends ChangeNotifier {
name: "customButtonScreen",
child: const CustomButtonScreen(),
),
_genericRoute(
name: "playerDecoderScreen",
child: const PlayerDecoderScreen(),
),
_genericRoute(name: "playerAudioScreen", child: const PlayerAudioScreen()),
_genericRoute(
name: "playerAdvancedScreen",
child: const PlayerAdvancedScreen(),
),
_genericRoute<Manga>(
name: "migrate",
builder: (manga) => MigrationScreen(manga: manga),

View file

@ -1164,7 +1164,7 @@ packages:
description:
path: media_kit
ref: HEAD
resolved-ref: "746465b7914fa524781813dc6e50ea87dcd686e5"
resolved-ref: a02ac2f7e6118cdab855ecacd194fa9ee6961a18
url: "https://github.com/Schnitzel5/media-kit.git"
source: git
version: "1.2.0"
@ -1221,7 +1221,7 @@ packages:
description:
path: media_kit_video
ref: HEAD
resolved-ref: "746465b7914fa524781813dc6e50ea87dcd686e5"
resolved-ref: a02ac2f7e6118cdab855ecacd194fa9ee6961a18
url: "https://github.com/Schnitzel5/media-kit.git"
source: git
version: "1.3.0"