mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-21 03:32:06 +00:00
Merge pull request #540 from Schnitzel5/enhance/mpv
enhanced mpv player
This commit is contained in:
commit
1de091b1d9
99 changed files with 10908 additions and 1328 deletions
|
|
@ -36,6 +36,12 @@
|
|||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="mangayomi" android:host="add-repo" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="Add custom button">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="mangayomi" android:host="add-button" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
|
||||
|
|
|
|||
BIN
assets/mangayomi_mpv.zip
Normal file
BIN
assets/mangayomi_mpv.zip
Normal file
Binary file not shown.
|
|
@ -12,6 +12,11 @@ class MChapterBridge {
|
|||
url: namedArgs.get<String?>('url'),
|
||||
dateUpload: namedArgs.get<String?>('dateUpload'),
|
||||
scanlator: namedArgs.get<String?>('scanlator'),
|
||||
isFiller: namedArgs.get<bool?>('isFiller'),
|
||||
thumbnailUrl: namedArgs.get<String?>('scanlator'),
|
||||
description: namedArgs.get<String?>('scanlator'),
|
||||
downloadSize: namedArgs.get<String?>('scanlator'),
|
||||
duration: namedArgs.get<String?>('scanlator'),
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
@ -20,6 +25,11 @@ class MChapterBridge {
|
|||
'url': (visitor, target) => (target as MChapter).url,
|
||||
'dateUpload': (visitor, target) => (target as MChapter).dateUpload,
|
||||
'scanlator': (visitor, target) => (target as MChapter).scanlator,
|
||||
'isFiller': (visitor, target) => (target as MChapter).isFiller,
|
||||
'thumbnailUrl': (visitor, target) => (target as MChapter).thumbnailUrl,
|
||||
'description': (visitor, target) => (target as MChapter).description,
|
||||
'downloadSize': (visitor, target) => (target as MChapter).downloadSize,
|
||||
'duration': (visitor, target) => (target as MChapter).duration,
|
||||
},
|
||||
setters: {
|
||||
'name': (visitor, target, value) =>
|
||||
|
|
@ -30,6 +40,16 @@ class MChapterBridge {
|
|||
(target as MChapter).dateUpload = value as String?,
|
||||
'scanlator': (visitor, target, value) =>
|
||||
(target as MChapter).scanlator = value as String?,
|
||||
'isFiller': (visitor, target, value) =>
|
||||
(target as MChapter).isFiller = value as bool?,
|
||||
'thumbnailUrl': (visitor, target, value) =>
|
||||
(target as MChapter).thumbnailUrl = value as String?,
|
||||
'description': (visitor, target, value) =>
|
||||
(target as MChapter).description = value as String?,
|
||||
'downloadSize': (visitor, target, value) =>
|
||||
(target as MChapter).downloadSize = value as String?,
|
||||
'duration': (visitor, target, value) =>
|
||||
(target as MChapter).duration = value as String?,
|
||||
},
|
||||
);
|
||||
void registerBridgedClasses(D4rt interpreter) {
|
||||
|
|
|
|||
|
|
@ -690,6 +690,7 @@ void Function() botToast(
|
|||
],
|
||||
bool onlyOne = true,
|
||||
bool? themeDark,
|
||||
bool showIcon = true,
|
||||
}) {
|
||||
final context = navigatorKey.currentState?.context;
|
||||
final assets = [
|
||||
|
|
@ -703,12 +704,14 @@ void Function() botToast(
|
|||
duration: Duration(seconds: second),
|
||||
animationDuration: Duration(milliseconds: animationDuration),
|
||||
animationReverseDuration: Duration(milliseconds: animationDuration),
|
||||
leading: (_) => Image.asset(
|
||||
(themeDark == null
|
||||
? (assets..shuffle()).first
|
||||
: assets[themeDark ? 0 : 1]),
|
||||
height: 25,
|
||||
),
|
||||
leading: showIcon
|
||||
? (_) => Image.asset(
|
||||
(themeDark == null
|
||||
? (assets..shuffle()).first
|
||||
: assets[themeDark ? 0 : 1]),
|
||||
height: 25,
|
||||
)
|
||||
: null,
|
||||
title: (_) => Text(title, style: TextStyle(fontSize: fontSize)),
|
||||
trailing: hasCloudFlare
|
||||
? (_) => OutlinedButton.icon(
|
||||
|
|
|
|||
|
|
@ -6,13 +6,41 @@ class MChapter {
|
|||
String? dateUpload;
|
||||
|
||||
String? scanlator;
|
||||
MChapter({this.name, this.url, this.dateUpload, this.scanlator});
|
||||
|
||||
bool? isFiller;
|
||||
|
||||
String? thumbnailUrl;
|
||||
|
||||
String? description;
|
||||
|
||||
/// video size
|
||||
String? downloadSize;
|
||||
|
||||
/// video duration
|
||||
String? duration;
|
||||
|
||||
MChapter({
|
||||
this.name,
|
||||
this.url,
|
||||
this.dateUpload,
|
||||
this.scanlator,
|
||||
this.isFiller = false,
|
||||
this.thumbnailUrl,
|
||||
this.description,
|
||||
this.downloadSize,
|
||||
this.duration,
|
||||
});
|
||||
factory MChapter.fromJson(Map<String, dynamic> json) {
|
||||
return MChapter(
|
||||
name: json['name'],
|
||||
url: json['url'],
|
||||
dateUpload: json['dateUpload'],
|
||||
scanlator: json['scanlator'],
|
||||
isFiller: json['isFiller'] ?? false,
|
||||
thumbnailUrl: json['thumbnailUrl'],
|
||||
description: json['description'],
|
||||
downloadSize: json['downloadSize'],
|
||||
duration: json['duration'],
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
|
@ -20,5 +48,10 @@ class MChapter {
|
|||
'url': url,
|
||||
'dateUpload': dateUpload,
|
||||
'scanlator': scanlator,
|
||||
'isFiller': isFiller,
|
||||
'thumbnailUrl': thumbnailUrl,
|
||||
'description': description,
|
||||
'downloadSize': downloadSize,
|
||||
'duration': duration,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@
|
|||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"a_week_ago": "A week ago",
|
||||
"next_week": "Next week",
|
||||
"add_to_library": "Add to library",
|
||||
"completed": "Completed",
|
||||
"ongoing": "Ongoing",
|
||||
|
|
@ -342,6 +343,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",
|
||||
|
|
@ -413,6 +415,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",
|
||||
|
|
@ -472,6 +475,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!",
|
||||
|
|
@ -485,5 +489,51 @@
|
|||
"rpc_show_cover_image": "Show current cover image in Discord",
|
||||
"sync_enable_histories": "Sync history data",
|
||||
"sync_enable_updates": "Sync update data",
|
||||
"sync_enable_settings": "Sync settings"
|
||||
"sync_enable_settings": "Sync settings",
|
||||
"enable_mpv": "Enable mpv shaders / scripts",
|
||||
"mpv_info": "Supports .js scripts under mpv/scripts/",
|
||||
"mpv_redownload": "Redownload mpv config files",
|
||||
"mpv_redownload_info": "Replaces old config files with new one!",
|
||||
"mpv_download": "MPV config files are required!\nDownload now?",
|
||||
"custom_buttons": "Custom buttons",
|
||||
"custom_buttons_info": "Execute lua code with custom buttons",
|
||||
"custom_buttons_edit": "Edit custom buttons",
|
||||
"custom_buttons_add": "Add custom button",
|
||||
"custom_buttons_added": "Custom button added!",
|
||||
"custom_buttons_delete": "Delete custom button",
|
||||
"custom_buttons_text": "Button text",
|
||||
"custom_buttons_text_req": "Button text required",
|
||||
"custom_buttons_js_code": "lua code",
|
||||
"custom_buttons_js_code_req": "lua code required",
|
||||
"custom_buttons_js_code_long": "lua code (on long press)",
|
||||
"custom_buttons_startup": "lua code (on startup)",
|
||||
"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",
|
||||
"calendar": "Calendar",
|
||||
"calendar_no_data": "No data yet.",
|
||||
"calendar_info": "The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!",
|
||||
"in_n_day": "in {days} day",
|
||||
"in_n_days": "in {days} days",
|
||||
"clear_library": "Clear library",
|
||||
"clear_library_desc": "Choose to clear all manga, anime and/or novel entries",
|
||||
"clear_library_input": "Type 'manga', 'anime' and/or 'novel' (separated by a comma) to remove all related entries",
|
||||
"recommendations": "Recommendations",
|
||||
"recommendations_similarity": "Similarity:"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -957,6 +957,12 @@ abstract class AppLocalizations {
|
|||
/// **'A week ago'**
|
||||
String get a_week_ago;
|
||||
|
||||
/// No description provided for @next_week.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Next week'**
|
||||
String get next_week;
|
||||
|
||||
/// No description provided for @add_to_library.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2127,6 +2133,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:
|
||||
|
|
@ -2553,6 +2565,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:
|
||||
|
|
@ -2907,6 +2925,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:
|
||||
|
|
@ -2990,6 +3014,282 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Sync settings'**
|
||||
String get sync_enable_settings;
|
||||
|
||||
/// No description provided for @enable_mpv.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable mpv shaders / scripts'**
|
||||
String get enable_mpv;
|
||||
|
||||
/// No description provided for @mpv_info.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Supports .js scripts under mpv/scripts/'**
|
||||
String get mpv_info;
|
||||
|
||||
/// No description provided for @mpv_redownload.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Redownload mpv config files'**
|
||||
String get mpv_redownload;
|
||||
|
||||
/// No description provided for @mpv_redownload_info.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Replaces old config files with new one!'**
|
||||
String get mpv_redownload_info;
|
||||
|
||||
/// No description provided for @mpv_download.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'MPV config files are required!\nDownload now?'**
|
||||
String get mpv_download;
|
||||
|
||||
/// No description provided for @custom_buttons.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Custom buttons'**
|
||||
String get custom_buttons;
|
||||
|
||||
/// No description provided for @custom_buttons_info.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Execute lua code with custom buttons'**
|
||||
String get custom_buttons_info;
|
||||
|
||||
/// No description provided for @custom_buttons_edit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit custom buttons'**
|
||||
String get custom_buttons_edit;
|
||||
|
||||
/// No description provided for @custom_buttons_add.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add custom button'**
|
||||
String get custom_buttons_add;
|
||||
|
||||
/// No description provided for @custom_buttons_added.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Custom button added!'**
|
||||
String get custom_buttons_added;
|
||||
|
||||
/// No description provided for @custom_buttons_delete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete custom button'**
|
||||
String get custom_buttons_delete;
|
||||
|
||||
/// No description provided for @custom_buttons_text.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Button text'**
|
||||
String get custom_buttons_text;
|
||||
|
||||
/// No description provided for @custom_buttons_text_req.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Button text required'**
|
||||
String get custom_buttons_text_req;
|
||||
|
||||
/// No description provided for @custom_buttons_js_code.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'lua code'**
|
||||
String get custom_buttons_js_code;
|
||||
|
||||
/// No description provided for @custom_buttons_js_code_req.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'lua code required'**
|
||||
String get custom_buttons_js_code_req;
|
||||
|
||||
/// No description provided for @custom_buttons_js_code_long.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'lua code (on long press)'**
|
||||
String get custom_buttons_js_code_long;
|
||||
|
||||
/// No description provided for @custom_buttons_startup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'lua code (on startup)'**
|
||||
String get custom_buttons_startup;
|
||||
|
||||
/// No description provided for @n_days.
|
||||
///
|
||||
/// 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;
|
||||
|
||||
/// No description provided for @calendar.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Calendar'**
|
||||
String get calendar;
|
||||
|
||||
/// No description provided for @calendar_no_data.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No data yet.'**
|
||||
String get calendar_no_data;
|
||||
|
||||
/// No description provided for @calendar_info.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!'**
|
||||
String get calendar_info;
|
||||
|
||||
/// No description provided for @in_n_day.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'in {days} day'**
|
||||
String in_n_day(Object days);
|
||||
|
||||
/// No description provided for @in_n_days.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'in {days} days'**
|
||||
String in_n_days(Object days);
|
||||
|
||||
/// No description provided for @clear_library.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Clear library'**
|
||||
String get clear_library;
|
||||
|
||||
/// No description provided for @clear_library_desc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose to clear all manga, anime and/or novel entries'**
|
||||
String get clear_library_desc;
|
||||
|
||||
/// No description provided for @clear_library_input.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries'**
|
||||
String get clear_library_input;
|
||||
|
||||
/// No description provided for @enable_pip.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable Picture-in-Picture (PiP)'**
|
||||
String get enable_pip;
|
||||
|
||||
/// No description provided for @enable_auto_pip.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter PiP automatically when moving out of app'**
|
||||
String get enable_auto_pip;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -448,6 +448,9 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'منذ أسبوع';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'إضافة إلى المكتبة';
|
||||
|
||||
|
|
@ -1074,6 +1077,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 => 'لاعب';
|
||||
|
||||
|
|
@ -1296,6 +1303,9 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get advanced => 'متقدم';
|
||||
|
||||
@override
|
||||
String get advanced_info => 'mpv config';
|
||||
|
||||
@override
|
||||
String get use_native_http_client => 'استخدام عميل HTTP الأصلي';
|
||||
|
||||
|
|
@ -1495,6 +1505,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';
|
||||
|
||||
|
|
@ -1539,4 +1552,155 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,6 +450,9 @@ class AppLocalizationsAs extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'এসপ্তাহৰ আগতে';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'পুথিভঁৰালত যোগ কৰক';
|
||||
|
||||
|
|
@ -1074,6 +1077,10 @@ class AppLocalizationsAs extends AppLocalizations {
|
|||
@override
|
||||
String get video_audio => 'অডিঅ’';
|
||||
|
||||
@override
|
||||
String get video_audio_info =>
|
||||
'Preferred languages, pitch correction, audio channels';
|
||||
|
||||
@override
|
||||
String get player => 'প্লেয়াৰ';
|
||||
|
||||
|
|
@ -1297,6 +1304,9 @@ class AppLocalizationsAs 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';
|
||||
|
||||
|
|
@ -1497,6 +1507,9 @@ class AppLocalizationsAs 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';
|
||||
|
||||
|
|
@ -1541,4 +1554,155 @@ class AppLocalizationsAs extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,6 +452,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'Vor einer Woche';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Zur Bibliothek hinzufügen';
|
||||
|
||||
|
|
@ -1079,6 +1082,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';
|
||||
|
||||
|
|
@ -1306,6 +1313,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';
|
||||
|
||||
|
|
@ -1507,6 +1517,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';
|
||||
|
||||
|
|
@ -1552,4 +1565,155 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,6 +450,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'A week ago';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Add to library';
|
||||
|
||||
|
|
@ -1073,6 +1076,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';
|
||||
|
||||
|
|
@ -1296,6 +1303,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';
|
||||
|
||||
|
|
@ -1496,6 +1506,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';
|
||||
|
||||
|
|
@ -1540,4 +1553,155 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,6 +454,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'Hace una semana';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Agregar a la biblioteca';
|
||||
|
||||
|
|
@ -1083,6 +1086,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';
|
||||
|
||||
|
|
@ -1310,6 +1317,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';
|
||||
|
||||
|
|
@ -1513,6 +1523,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';
|
||||
|
||||
|
|
@ -1557,6 +1570,157 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
||||
/// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`).
|
||||
|
|
|
|||
|
|
@ -456,6 +456,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'Il y a une semaine';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Ajouter à la bibliothèque';
|
||||
|
||||
|
|
@ -1085,6 +1088,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';
|
||||
|
||||
|
|
@ -1312,6 +1319,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';
|
||||
|
||||
|
|
@ -1514,6 +1524,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';
|
||||
|
||||
|
|
@ -1558,4 +1571,155 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,6 +450,9 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'एक सप्ताह पहले';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'पुस्तकालय में जोड़ें';
|
||||
|
||||
|
|
@ -1075,6 +1078,10 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||
@override
|
||||
String get video_audio => 'ऑडियो';
|
||||
|
||||
@override
|
||||
String get video_audio_info =>
|
||||
'Preferred languages, pitch correction, audio channels';
|
||||
|
||||
@override
|
||||
String get player => 'प्लेयर';
|
||||
|
||||
|
|
@ -1298,6 +1305,9 @@ class AppLocalizationsHi 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';
|
||||
|
||||
|
|
@ -1498,6 +1508,9 @@ class AppLocalizationsHi 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';
|
||||
|
||||
|
|
@ -1542,4 +1555,155 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,6 +454,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'Seminggu yang Lalu';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Tambahkan ke Perpustakaan';
|
||||
|
||||
|
|
@ -1079,6 +1082,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';
|
||||
|
||||
|
|
@ -1303,6 +1310,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';
|
||||
|
||||
|
|
@ -1502,6 +1512,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';
|
||||
|
||||
|
|
@ -1546,4 +1559,155 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,6 +454,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'Una settimana fa';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Aggiungi alla biblioteca';
|
||||
|
||||
|
|
@ -1082,6 +1085,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';
|
||||
|
||||
|
|
@ -1310,6 +1317,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';
|
||||
|
||||
|
|
@ -1511,6 +1521,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';
|
||||
|
||||
|
|
@ -1555,4 +1568,155 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,6 +454,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'Uma semana atrás';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Adicionar à biblioteca';
|
||||
|
||||
|
|
@ -1081,6 +1084,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';
|
||||
|
||||
|
|
@ -1307,6 +1314,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';
|
||||
|
||||
|
|
@ -1510,6 +1520,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';
|
||||
|
||||
|
|
@ -1554,6 +1567,157 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
||||
/// The translations for Portuguese, as used in Brazil (`pt_BR`).
|
||||
|
|
|
|||
|
|
@ -455,6 +455,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'Неделю назад';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Добавить в библиотеку';
|
||||
|
||||
|
|
@ -1084,6 +1087,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 => 'Игрок';
|
||||
|
||||
|
|
@ -1310,6 +1317,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get advanced => 'Продвинутые';
|
||||
|
||||
@override
|
||||
String get advanced_info => 'mpv config';
|
||||
|
||||
@override
|
||||
String get use_native_http_client => 'Использовать нативный HTTP-клиент';
|
||||
|
||||
|
|
@ -1512,6 +1522,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';
|
||||
|
||||
|
|
@ -1556,4 +1569,155 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,6 +450,9 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'สัปดาห์ที่แล้ว';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'เพิ่มไปที่ชั้นหนังสือ';
|
||||
|
||||
|
|
@ -1073,6 +1076,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 => 'ตัวเล่น';
|
||||
|
||||
|
|
@ -1297,6 +1304,9 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
@override
|
||||
String get advanced => 'ขั้นสูง';
|
||||
|
||||
@override
|
||||
String get advanced_info => 'mpv config';
|
||||
|
||||
@override
|
||||
String get use_native_http_client => 'ใช้ไคลเอนต์ HTTP พื้นเมือง';
|
||||
|
||||
|
|
@ -1496,6 +1506,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';
|
||||
|
||||
|
|
@ -1540,4 +1553,155 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,6 +450,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => 'Bir Hafta Önce';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => 'Kütüphaneye Ekle';
|
||||
|
||||
|
|
@ -1077,6 +1080,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';
|
||||
|
||||
|
|
@ -1302,6 +1309,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';
|
||||
|
||||
|
|
@ -1502,6 +1512,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';
|
||||
|
||||
|
|
@ -1546,4 +1559,155 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -442,6 +442,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get a_week_ago => '一周前';
|
||||
|
||||
@override
|
||||
String get next_week => 'Next week';
|
||||
|
||||
@override
|
||||
String get add_to_library => '添加到图书馆';
|
||||
|
||||
|
|
@ -1057,6 +1060,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 => '播放器';
|
||||
|
||||
|
|
@ -1274,6 +1281,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get advanced => '高级';
|
||||
|
||||
@override
|
||||
String get advanced_info => 'mpv config';
|
||||
|
||||
@override
|
||||
String get use_native_http_client => '使用本地 HTTP 客户端';
|
||||
|
||||
|
|
@ -1467,6 +1477,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';
|
||||
|
||||
|
|
@ -1511,4 +1524,155 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get sync_enable_settings => 'Sync settings';
|
||||
|
||||
@override
|
||||
String get enable_mpv => 'Enable mpv shaders / scripts';
|
||||
|
||||
@override
|
||||
String get mpv_info => 'Supports .js scripts under mpv/scripts/';
|
||||
|
||||
@override
|
||||
String get mpv_redownload => 'Redownload mpv config files';
|
||||
|
||||
@override
|
||||
String get mpv_redownload_info => 'Replaces old config files with new one!';
|
||||
|
||||
@override
|
||||
String get mpv_download => 'MPV config files are required!\nDownload now?';
|
||||
|
||||
@override
|
||||
String get custom_buttons => 'Custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_info => 'Execute lua code with custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_edit => 'Edit custom buttons';
|
||||
|
||||
@override
|
||||
String get custom_buttons_add => 'Add custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_added => 'Custom button added!';
|
||||
|
||||
@override
|
||||
String get custom_buttons_delete => 'Delete custom button';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text => 'Button text';
|
||||
|
||||
@override
|
||||
String get custom_buttons_text_req => 'Button text required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code => 'lua code';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_req => 'lua code required';
|
||||
|
||||
@override
|
||||
String get custom_buttons_js_code_long => 'lua code (on long press)';
|
||||
|
||||
@override
|
||||
String get custom_buttons_startup => 'lua code (on startup)';
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
@override
|
||||
String get calendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get calendar_no_data => 'No data yet.';
|
||||
|
||||
@override
|
||||
String get calendar_info =>
|
||||
'The calendar is only able to predict the next chapter upload based on the older uploads. Some data might not be 100% accurate!';
|
||||
|
||||
@override
|
||||
String in_n_day(Object days) {
|
||||
return 'in $days day';
|
||||
}
|
||||
|
||||
@override
|
||||
String in_n_days(Object days) {
|
||||
return 'in $days days';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clear_library => 'Clear library';
|
||||
|
||||
@override
|
||||
String get clear_library_desc =>
|
||||
'Choose to clear all manga, anime and/or novel entries';
|
||||
|
||||
@override
|
||||
String get clear_library_input =>
|
||||
'Type \'manga\', \'anime\' and/or \'novel\' (separated by a comma) to remove all related entries';
|
||||
|
||||
@override
|
||||
String get enable_pip => 'Enable Picture-in-Picture (PiP)';
|
||||
|
||||
@override
|
||||
String get enable_auto_pip =>
|
||||
'Enter PiP automatically when moving out of app';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
@ -13,6 +14,7 @@ import 'package:hive_flutter/adapters.dart';
|
|||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/models/custom_button.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
|
|
@ -31,9 +33,11 @@ import 'package:mangayomi/utils/url_protocol/api.dart';
|
|||
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_provider.dart';
|
||||
import 'package:mangayomi/modules/library/providers/file_scanner.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
|
||||
late Isar isar;
|
||||
DiscordRPC? discordRpc;
|
||||
|
|
@ -94,6 +98,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
super.initState();
|
||||
initializeDateFormatting();
|
||||
_initDeepLinks();
|
||||
_setupMpvConfig();
|
||||
unawaited(ref.read(scanLocalLibraryProvider.future));
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
|
|
@ -141,7 +146,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
|
||||
Future<void> _initDeepLinks() async {
|
||||
_appLinks = AppLinks();
|
||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
|
||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
|
||||
if (uri == lastUri) return; // Debouncing Deep Links
|
||||
lastUri = uri;
|
||||
switch (uri.host) {
|
||||
|
|
@ -220,6 +225,62 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
},
|
||||
);
|
||||
break;
|
||||
case "add-button":
|
||||
final buttonDataRaw = uri.queryParametersAll["button"];
|
||||
final context = navigatorKey.currentContext;
|
||||
if (context == null || !context.mounted || buttonDataRaw == null) {
|
||||
return;
|
||||
}
|
||||
final l10n = context.l10n;
|
||||
for (final buttonRaw in buttonDataRaw) {
|
||||
final buttonData = jsonDecode(
|
||||
utf8.decode(base64.decode(buttonRaw)),
|
||||
);
|
||||
if (buttonData is Map<String, dynamic>) {
|
||||
final customButton = CustomButton.fromJson(buttonData);
|
||||
await showDialog(
|
||||
context: navigatorKey.currentContext!,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.custom_buttons_add),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${l10n.name}: ${customButton.title ?? 'Unknown'}",
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(l10n.cancel),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
FilledButton(
|
||||
child: Text(l10n.add),
|
||||
onPressed: () async {
|
||||
if (context.mounted) Navigator.of(context).pop();
|
||||
await isar.writeTxn(() async {
|
||||
await isar.customButtons.put(
|
||||
customButton
|
||||
..pos = await isar.customButtons.count()
|
||||
..isFavourite = false
|
||||
..id = null
|
||||
..updatedAt =
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
});
|
||||
botToast(l10n.custom_buttons_added);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
|
|
@ -242,6 +303,38 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _setupMpvConfig() async {
|
||||
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 (filesMissing) {
|
||||
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") || file.name.endsWith(".lua"))) {
|
||||
final scriptFile = File('$scriptsDir/${file.name.split("/").last}');
|
||||
await scriptFile.writeAsBytes(file.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AllowScrollBehavior extends MaterialScrollBehavior {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,18 @@ class Chapter {
|
|||
///Only for local archive Comic
|
||||
String? archivePath;
|
||||
|
||||
bool? isFiller;
|
||||
|
||||
String? thumbnailUrl;
|
||||
|
||||
String? description;
|
||||
|
||||
/// video size
|
||||
String? downloadSize;
|
||||
|
||||
/// video duration
|
||||
String? duration;
|
||||
|
||||
int? updatedAt;
|
||||
|
||||
final manga = IsarLink<Manga>();
|
||||
|
|
@ -41,6 +53,11 @@ class Chapter {
|
|||
this.isRead = false,
|
||||
this.lastPageRead = '',
|
||||
this.archivePath = '',
|
||||
this.isFiller = false,
|
||||
this.thumbnailUrl,
|
||||
this.description,
|
||||
this.downloadSize,
|
||||
this.duration,
|
||||
this.updatedAt = 0,
|
||||
});
|
||||
|
||||
|
|
@ -55,6 +72,11 @@ class Chapter {
|
|||
name = json['name'];
|
||||
scanlator = json['scanlator'];
|
||||
url = json['url'];
|
||||
isFiller = json['isFiller'] ?? false;
|
||||
thumbnailUrl = json['thumbnailUrl'];
|
||||
description = json['description'];
|
||||
downloadSize = json['downloadSize'];
|
||||
duration = json['duration'];
|
||||
updatedAt = json['updatedAt'];
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +91,11 @@ class Chapter {
|
|||
'name': name,
|
||||
'scanlator': scanlator,
|
||||
'url': url,
|
||||
'isFiller': isFiller,
|
||||
'thumbnailUrl': thumbnailUrl,
|
||||
'description': description,
|
||||
'downloadSize': downloadSize,
|
||||
'duration': duration,
|
||||
'updatedAt': updatedAt ?? 0,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
95
lib/models/custom_button.dart
Normal file
95
lib/models/custom_button.dart
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:isar/isar.dart';
|
||||
part 'custom_button.g.dart';
|
||||
|
||||
@collection
|
||||
@Name("CustomButton")
|
||||
class CustomButton {
|
||||
Id? id;
|
||||
|
||||
String? title;
|
||||
|
||||
String? codePress;
|
||||
|
||||
String? codeLongPress;
|
||||
|
||||
String? codeStartup;
|
||||
|
||||
bool? isFavourite;
|
||||
|
||||
int? pos;
|
||||
|
||||
int? updatedAt;
|
||||
|
||||
CustomButton({
|
||||
this.id = Isar.autoIncrement,
|
||||
required this.title,
|
||||
required this.codePress,
|
||||
this.codeLongPress = "",
|
||||
this.codeStartup = "",
|
||||
this.isFavourite = false,
|
||||
required this.pos,
|
||||
this.updatedAt = 0,
|
||||
});
|
||||
|
||||
String getButtonStartup(int primaryId) {
|
||||
final isPrimary = primaryId == id ? "true" : "false";
|
||||
return codeStartup
|
||||
?.replaceAll("\$id", "$id")
|
||||
.replaceAll("\$isPrimary", isPrimary) ??
|
||||
"";
|
||||
}
|
||||
|
||||
String getButtonPress(int primaryId) {
|
||||
final isPrimary = primaryId == id ? "true" : "false";
|
||||
return codePress
|
||||
?.replaceAll("\$id", "$id")
|
||||
.replaceAll("\$isPrimary", isPrimary) ??
|
||||
"";
|
||||
}
|
||||
|
||||
String getButtonLongPress(int primaryId) {
|
||||
final isPrimary = primaryId == id ? "true" : "false";
|
||||
return codeLongPress
|
||||
?.replaceAll("\$id", "$id")
|
||||
.replaceAll("\$isPrimary", isPrimary) ??
|
||||
"";
|
||||
}
|
||||
|
||||
CustomButton.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
title = json['title'];
|
||||
codePress = json['codePress'];
|
||||
codeLongPress = json['codeLongPress'];
|
||||
codeStartup = json['codeStartup'];
|
||||
isFavourite = json['isFavourite'];
|
||||
pos = json['pos'];
|
||||
updatedAt = json['updatedAt'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'codePress': codePress,
|
||||
'codeLongPress': codeLongPress,
|
||||
'codeStartup': codeStartup,
|
||||
'isFavourite': isFavourite,
|
||||
'pos': pos,
|
||||
'updatedAt': updatedAt ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
class ActiveCustomButton {
|
||||
String currentTitle;
|
||||
bool visible;
|
||||
CustomButton button;
|
||||
Function() onPress;
|
||||
Function() onLongPress;
|
||||
|
||||
ActiveCustomButton({
|
||||
required this.currentTitle,
|
||||
required this.visible,
|
||||
required this.button,
|
||||
required this.onPress,
|
||||
required this.onLongPress,
|
||||
});
|
||||
}
|
||||
1413
lib/models/custom_button.g.dart
Normal file
1413
lib/models/custom_button.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -49,6 +49,9 @@ class Manga {
|
|||
|
||||
String? customCoverFromTracker;
|
||||
|
||||
/// only update X days after `lastUpdate`
|
||||
int? smartUpdateDays;
|
||||
|
||||
int? updatedAt;
|
||||
|
||||
@Backlink(to: "manga")
|
||||
|
|
@ -76,6 +79,7 @@ class Manga {
|
|||
this.isLocalArchive = false,
|
||||
this.customCoverImage,
|
||||
this.customCoverFromTracker,
|
||||
this.smartUpdateDays,
|
||||
this.updatedAt = 0,
|
||||
});
|
||||
|
||||
|
|
@ -101,6 +105,7 @@ class Manga {
|
|||
source = json['source'];
|
||||
status = Status.values[json['status']];
|
||||
customCoverFromTracker = json['customCoverFromTracker'];
|
||||
smartUpdateDays = json['smartUpdateDays'];
|
||||
updatedAt = json['updatedAt'];
|
||||
}
|
||||
|
||||
|
|
@ -125,6 +130,7 @@ class Manga {
|
|||
'source': source,
|
||||
'status': status.index,
|
||||
'customCoverFromTracker': customCoverFromTracker,
|
||||
'smartUpdateDays': smartUpdateDays,
|
||||
'updatedAt': updatedAt ?? 0,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,19 +108,24 @@ const MangaSchema = CollectionSchema(
|
|||
name: r'name',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'source': PropertySchema(
|
||||
r'smartUpdateDays': PropertySchema(
|
||||
id: 18,
|
||||
name: r'smartUpdateDays',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'source': PropertySchema(
|
||||
id: 19,
|
||||
name: r'source',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'status': PropertySchema(
|
||||
id: 19,
|
||||
id: 20,
|
||||
name: r'status',
|
||||
type: IsarType.byte,
|
||||
enumMap: _MangastatusEnumValueMap,
|
||||
),
|
||||
r'updatedAt': PropertySchema(
|
||||
id: 20,
|
||||
id: 21,
|
||||
name: r'updatedAt',
|
||||
type: IsarType.long,
|
||||
)
|
||||
|
|
@ -258,9 +263,10 @@ void _mangaSerialize(
|
|||
writer.writeLong(offsets[15], object.lastUpdate);
|
||||
writer.writeString(offsets[16], object.link);
|
||||
writer.writeString(offsets[17], object.name);
|
||||
writer.writeString(offsets[18], object.source);
|
||||
writer.writeByte(offsets[19], object.status.index);
|
||||
writer.writeLong(offsets[20], object.updatedAt);
|
||||
writer.writeLong(offsets[18], object.smartUpdateDays);
|
||||
writer.writeString(offsets[19], object.source);
|
||||
writer.writeByte(offsets[20], object.status.index);
|
||||
writer.writeLong(offsets[21], object.updatedAt);
|
||||
}
|
||||
|
||||
Manga _mangaDeserialize(
|
||||
|
|
@ -290,10 +296,11 @@ Manga _mangaDeserialize(
|
|||
lastUpdate: reader.readLongOrNull(offsets[15]),
|
||||
link: reader.readStringOrNull(offsets[16]),
|
||||
name: reader.readStringOrNull(offsets[17]),
|
||||
source: reader.readStringOrNull(offsets[18]),
|
||||
status: _MangastatusValueEnumMap[reader.readByteOrNull(offsets[19])] ??
|
||||
smartUpdateDays: reader.readLongOrNull(offsets[18]),
|
||||
source: reader.readStringOrNull(offsets[19]),
|
||||
status: _MangastatusValueEnumMap[reader.readByteOrNull(offsets[20])] ??
|
||||
Status.ongoing,
|
||||
updatedAt: reader.readLongOrNull(offsets[20]),
|
||||
updatedAt: reader.readLongOrNull(offsets[21]),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
|
@ -343,11 +350,13 @@ P _mangaDeserializeProp<P>(
|
|||
case 17:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 18:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 19:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 20:
|
||||
return (_MangastatusValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||
Status.ongoing) as P;
|
||||
case 20:
|
||||
case 21:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
|
|
@ -2591,6 +2600,75 @@ extension MangaQueryFilter on QueryBuilder<Manga, Manga, QFilterCondition> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterFilterCondition> smartUpdateDaysIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'smartUpdateDays',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterFilterCondition> smartUpdateDaysIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'smartUpdateDays',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterFilterCondition> smartUpdateDaysEqualTo(
|
||||
int? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'smartUpdateDays',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterFilterCondition> smartUpdateDaysGreaterThan(
|
||||
int? value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'smartUpdateDays',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterFilterCondition> smartUpdateDaysLessThan(
|
||||
int? value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'smartUpdateDays',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterFilterCondition> smartUpdateDaysBetween(
|
||||
int? lower,
|
||||
int? upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'smartUpdateDays',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterFilterCondition> sourceIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
|
|
@ -3100,6 +3178,18 @@ extension MangaQuerySortBy on QueryBuilder<Manga, Manga, QSortBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterSortBy> sortBySmartUpdateDays() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'smartUpdateDays', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterSortBy> sortBySmartUpdateDaysDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'smartUpdateDays', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterSortBy> sortBySource() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'source', Sort.asc);
|
||||
|
|
@ -3330,6 +3420,18 @@ extension MangaQuerySortThenBy on QueryBuilder<Manga, Manga, QSortThenBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterSortBy> thenBySmartUpdateDays() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'smartUpdateDays', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterSortBy> thenBySmartUpdateDaysDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'smartUpdateDays', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QAfterSortBy> thenBySource() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'source', Sort.asc);
|
||||
|
|
@ -3485,6 +3587,12 @@ extension MangaQueryWhereDistinct on QueryBuilder<Manga, Manga, QDistinct> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QDistinct> distinctBySmartUpdateDays() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'smartUpdateDays');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, Manga, QDistinct> distinctBySource(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
|
@ -3621,6 +3729,12 @@ extension MangaQueryProperty on QueryBuilder<Manga, Manga, QQueryProperty> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, int?, QQueryOperations> smartUpdateDaysProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'smartUpdateDays');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Manga, String?, QQueryOperations> sourceProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'source');
|
||||
|
|
|
|||
|
|
@ -258,11 +258,28 @@ class Settings {
|
|||
|
||||
bool? rpcShowCoverImage;
|
||||
|
||||
bool? useMpvConfig;
|
||||
|
||||
@enumerated
|
||||
late DebandingType debandingType;
|
||||
|
||||
bool? enableGpuNext;
|
||||
|
||||
bool? useYUV420P;
|
||||
|
||||
String? audioPreferredLanguages;
|
||||
|
||||
bool? enableAudioPitchCorrection;
|
||||
|
||||
@enumerated
|
||||
late AudioChannel audioChannels;
|
||||
|
||||
int? volumeBoostCap;
|
||||
|
||||
bool? downloadedOnlyMode;
|
||||
|
||||
late AlgorithmWeights? algorithmWeights;
|
||||
|
||||
|
||||
Settings({
|
||||
this.id = 227,
|
||||
this.updatedAt = 0,
|
||||
|
|
@ -378,6 +395,14 @@ class Settings {
|
|||
this.rpcShowReadingWatchingProgress = true,
|
||||
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,
|
||||
this.downloadedOnlyMode = false,
|
||||
this.algorithmWeights,
|
||||
});
|
||||
|
|
@ -601,6 +626,16 @@ class Settings {
|
|||
rpcShowReadingWatchingProgress = json['rpcShowReadingWatchingProgress'];
|
||||
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'];
|
||||
downloadedOnlyMode = json['downloadedOnlyMode'];
|
||||
algorithmWeights = json['algorithmWeights'] != null
|
||||
? AlgorithmWeights.fromJson(json['algorithmWeights'])
|
||||
|
|
@ -743,12 +778,34 @@ class Settings {
|
|||
'rpcShowReadingWatchingProgress': rpcShowReadingWatchingProgress,
|
||||
'rpcShowTitle': rpcShowTitle,
|
||||
'rpcShowCoverImage': rpcShowCoverImage,
|
||||
'useMpvConfig': useMpvConfig,
|
||||
'debandingType': debandingType.index,
|
||||
'enableGpuNext': enableGpuNext,
|
||||
'useYUV420P': useYUV420P,
|
||||
'audioPreferredLanguages': audioPreferredLanguages,
|
||||
'enableAudioPitchCorrection': enableAudioPitchCorrection,
|
||||
'audioChannels': audioChannels.index,
|
||||
'volumeBoostCap': volumeBoostCap,
|
||||
'downloadedOnlyMode': downloadedOnlyMode,
|
||||
if (algorithmWeights != null)
|
||||
'algorithmWeights': algorithmWeights!.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
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
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/modules/anime/widgets/custom_track_shape.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ class CustomSeekBar extends StatefulWidget {
|
|||
final Duration? delta;
|
||||
final Function(Duration)? onSeekStart;
|
||||
final Function(Duration)? onSeekEnd;
|
||||
final ValueNotifier<List<(String, int)>> chapterMarks;
|
||||
|
||||
const CustomSeekBar({
|
||||
super.key,
|
||||
|
|
@ -16,6 +18,7 @@ class CustomSeekBar extends StatefulWidget {
|
|||
this.onSeekEnd,
|
||||
required this.player,
|
||||
this.delta,
|
||||
required this.chapterMarks,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -90,6 +93,14 @@ class CustomSeekBarState extends State<CustomSeekBar> {
|
|||
data: SliderTheme.of(context).copyWith(
|
||||
trackHeight: isDesktop ? null : 3,
|
||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 5.0),
|
||||
trackShape: CustomTrackShape(
|
||||
currentPosition: clampedValue,
|
||||
bufferPosition: max(buffer.inMilliseconds.toDouble(), 0),
|
||||
maxValue: maxValue < 1 ? 1 : maxValue,
|
||||
minValue: 0,
|
||||
chapterMarks: widget.chapterMarks.value,
|
||||
chapterMarkWidth: 10,
|
||||
),
|
||||
),
|
||||
child: Slider(
|
||||
max: maxValue,
|
||||
|
|
|
|||
200
lib/modules/anime/widgets/custom_track_shape.dart
Normal file
200
lib/modules/anime/widgets/custom_track_shape.dart
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomTrackShape extends SliderTrackShape {
|
||||
final double maxValue;
|
||||
final double minValue;
|
||||
final double currentPosition;
|
||||
final double bufferPosition;
|
||||
final List<(String, int)> chapterMarks;
|
||||
final double chapterMarkWidth;
|
||||
double trackWidth;
|
||||
|
||||
CustomTrackShape({
|
||||
required this.maxValue,
|
||||
required this.minValue,
|
||||
required this.currentPosition,
|
||||
required this.bufferPosition,
|
||||
required this.chapterMarks,
|
||||
this.chapterMarkWidth = 3,
|
||||
this.trackWidth = 5,
|
||||
});
|
||||
|
||||
@override
|
||||
Rect getPreferredRect({
|
||||
required RenderBox parentBox,
|
||||
Offset offset = Offset.zero,
|
||||
required SliderThemeData sliderTheme,
|
||||
bool? isEnabled,
|
||||
bool? isDiscrete,
|
||||
}) {
|
||||
final double thumbWidth = sliderTheme.thumbShape!
|
||||
.getPreferredSize(isEnabled ?? true, isDiscrete ?? false)
|
||||
.width;
|
||||
final double trackHeight = sliderTheme.trackHeight!;
|
||||
|
||||
final double trackTop =
|
||||
offset.dy + (parentBox.size.height - trackHeight) / 2;
|
||||
final double trackLeft = offset.dx + thumbWidth / 2;
|
||||
trackWidth = parentBox.size.width - thumbWidth;
|
||||
|
||||
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset offset, {
|
||||
required RenderBox parentBox,
|
||||
required SliderThemeData sliderTheme,
|
||||
required Animation<double> enableAnimation,
|
||||
required Offset thumbCenter,
|
||||
Offset? secondaryOffset,
|
||||
bool? isEnabled,
|
||||
bool? isDiscrete,
|
||||
required TextDirection textDirection,
|
||||
}) {
|
||||
if (sliderTheme.trackHeight == 0) return;
|
||||
final Rect trackRect = getPreferredRect(
|
||||
parentBox: parentBox,
|
||||
offset: offset,
|
||||
sliderTheme: sliderTheme,
|
||||
isEnabled: isEnabled,
|
||||
isDiscrete: isDiscrete,
|
||||
);
|
||||
double currentPositionWidth = (trackWidth / maxValue) * currentPosition;
|
||||
double bufferPositionWidth = (trackWidth / maxValue) * bufferPosition;
|
||||
|
||||
_drawActiveThumb(context, sliderTheme, trackRect, currentPositionWidth);
|
||||
_drawBufferThumb(
|
||||
context,
|
||||
sliderTheme,
|
||||
trackRect,
|
||||
currentPositionWidth,
|
||||
bufferPositionWidth,
|
||||
);
|
||||
_drawInactiveThumb(context, sliderTheme, trackRect, currentPositionWidth);
|
||||
|
||||
for (final mark in chapterMarks) {
|
||||
double markPositionWidth = (trackWidth / maxValue) * mark.$2;
|
||||
_drawChapterMark(context, sliderTheme, trackRect, markPositionWidth);
|
||||
}
|
||||
}
|
||||
|
||||
void _drawActiveThumb(
|
||||
PaintingContext context,
|
||||
SliderThemeData sliderTheme,
|
||||
Rect trackRect,
|
||||
double currentPositionWidth,
|
||||
) {
|
||||
final Paint defaultPathPaint = Paint()
|
||||
..color = sliderTheme.activeTrackColor!
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final defaultPathSegment = Path()
|
||||
..addRect(
|
||||
Rect.fromPoints(
|
||||
Offset(trackRect.left, trackRect.top),
|
||||
Offset(trackRect.left + currentPositionWidth, trackRect.bottom),
|
||||
),
|
||||
)
|
||||
..lineTo(trackRect.left, trackRect.bottom)
|
||||
..arcTo(
|
||||
Rect.fromPoints(
|
||||
Offset(trackRect.left + 5, trackRect.top),
|
||||
Offset(trackRect.left - 5, trackRect.bottom),
|
||||
),
|
||||
-pi * 3 / 2,
|
||||
pi,
|
||||
false,
|
||||
);
|
||||
|
||||
context.canvas.drawPath(defaultPathSegment, defaultPathPaint);
|
||||
}
|
||||
|
||||
void _drawBufferThumb(
|
||||
PaintingContext context,
|
||||
SliderThemeData sliderTheme,
|
||||
Rect trackRect,
|
||||
double currentPositionWidth,
|
||||
double bufferPositionWidth,
|
||||
) {
|
||||
final Paint defaultPathPaint = Paint()
|
||||
..color = sliderTheme.secondaryActiveTrackColor!
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final defaultPathSegment = Path()
|
||||
..addRect(
|
||||
Rect.fromPoints(
|
||||
Offset(trackRect.left + currentPositionWidth, trackRect.top),
|
||||
Offset(trackRect.left + bufferPositionWidth, trackRect.bottom),
|
||||
),
|
||||
)
|
||||
..lineTo(trackRect.left, trackRect.bottom)
|
||||
..arcTo(
|
||||
Rect.fromPoints(
|
||||
Offset(trackRect.left + 5, trackRect.top),
|
||||
Offset(trackRect.left - 5, trackRect.bottom),
|
||||
),
|
||||
-pi * 3 / 2,
|
||||
pi,
|
||||
false,
|
||||
);
|
||||
|
||||
context.canvas.drawPath(defaultPathSegment, defaultPathPaint);
|
||||
}
|
||||
|
||||
void _drawInactiveThumb(
|
||||
PaintingContext context,
|
||||
SliderThemeData sliderTheme,
|
||||
Rect trackRect,
|
||||
double currentPositionWidth,
|
||||
) {
|
||||
final unselectedPathPaint = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = sliderTheme.inactiveTrackColor!;
|
||||
|
||||
final unselectedPathSegment = Path()
|
||||
..addRect(
|
||||
Rect.fromPoints(
|
||||
Offset(trackRect.right, trackRect.top),
|
||||
Offset(trackRect.left + currentPositionWidth, trackRect.bottom),
|
||||
),
|
||||
)
|
||||
..addArc(
|
||||
Rect.fromPoints(
|
||||
Offset(trackRect.right - 5, trackRect.bottom),
|
||||
Offset(trackRect.right + 5, trackRect.top),
|
||||
),
|
||||
-pi / 2,
|
||||
pi,
|
||||
);
|
||||
|
||||
context.canvas.drawPath(unselectedPathSegment, unselectedPathPaint);
|
||||
}
|
||||
|
||||
void _drawChapterMark(
|
||||
PaintingContext context,
|
||||
SliderThemeData sliderTheme,
|
||||
Rect trackRect,
|
||||
double markPositionWidth,
|
||||
) {
|
||||
final Paint borderPaint = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final pathSegmentSelected = Path()
|
||||
..addRect(
|
||||
Rect.fromPoints(
|
||||
Offset(trackRect.left + markPositionWidth, trackRect.top),
|
||||
Offset(
|
||||
trackRect.left + markPositionWidth + chapterMarkWidth,
|
||||
trackRect.bottom,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
context.canvas.drawPath(pathSegmentSelected, borderPaint);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import 'package:mangayomi/modules/anime/providers/anime_player_controller_provid
|
|||
import 'package:mangayomi/modules/anime/widgets/custom_seekbar.dart';
|
||||
import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
|
@ -24,6 +25,7 @@ class DesktopControllerWidget extends ConsumerStatefulWidget {
|
|||
final Widget seekToWidget;
|
||||
final int defaultSkipIntroLength;
|
||||
final void Function(bool) desktopFullScreenPlayer;
|
||||
final ValueNotifier<List<(String, int)>> chapterMarks;
|
||||
const DesktopControllerWidget({
|
||||
super.key,
|
||||
required this.videoController,
|
||||
|
|
@ -36,6 +38,7 @@ class DesktopControllerWidget extends ConsumerStatefulWidget {
|
|||
required this.doubleSpeed,
|
||||
required this.defaultSkipIntroLength,
|
||||
required this.desktopFullScreenPlayer,
|
||||
required this.chapterMarks,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -215,6 +218,48 @@ class _DesktopControllerWidgetState
|
|||
final desktopFullScreenPlayer = widget.desktopFullScreenPlayer;
|
||||
await _changeFullScreen(ref, desktopFullScreenPlayer, value: false);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit0, control: true): () {
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"clear_anime",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit1, control: true): () {
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_a",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit2, control: true): () {
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_b",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit3, control: true): () {
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_c",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit4, control: true): () {
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_aa",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit5, control: true): () {
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_bb",
|
||||
]);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.digit6, control: true): () {
|
||||
(widget.videoController.player.platform as NativePlayer).command([
|
||||
"script-message",
|
||||
"set_anime_ca",
|
||||
]);
|
||||
},
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
|
|
@ -439,6 +484,7 @@ class _DesktopControllerWidgetState
|
|||
widget.tempDuration(null);
|
||||
},
|
||||
player: widget.videoController.player,
|
||||
chapterMarks: widget.chapterMarks,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class MobileControllerWidget extends ConsumerStatefulWidget {
|
|||
final Widget topButtonBarWidget;
|
||||
final GlobalKey<VideoState> videoStatekey;
|
||||
final Widget bottomButtonBarWidget;
|
||||
final ValueNotifier<List<(String, int)>> chapterMarks;
|
||||
const MobileControllerWidget({
|
||||
super.key,
|
||||
required this.videoController,
|
||||
|
|
@ -32,6 +33,7 @@ class MobileControllerWidget extends ConsumerStatefulWidget {
|
|||
required this.streamController,
|
||||
required this.videoStatekey,
|
||||
required this.doubleSpeed,
|
||||
required this.chapterMarks,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -464,6 +466,7 @@ class _MobileControllerWidgetState
|
|||
});
|
||||
},
|
||||
player: widget.videoController.player,
|
||||
chapterMarks: widget.chapterMarks,
|
||||
),
|
||||
),
|
||||
widget.bottomButtonBarWidget,
|
||||
|
|
@ -491,6 +494,7 @@ class _MobileControllerWidgetState
|
|||
child: CustomSeekBar(
|
||||
delta: _seekBarDeltaValueNotifier,
|
||||
player: widget.videoController.player,
|
||||
chapterMarks: widget.chapterMarks,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
419
lib/modules/calendar/calendar_screen.dart
Normal file
419
lib/modules/calendar/calendar_screen.dart
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/modules/calendar/providers/calendar_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_sliver_grouped_list_view.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/constant.dart';
|
||||
import 'package:mangayomi/utils/date.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/utils/headers.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
class CalendarScreen extends ConsumerStatefulWidget {
|
||||
final ItemType? itemType;
|
||||
const CalendarScreen({super.key, this.itemType});
|
||||
|
||||
@override
|
||||
ConsumerState<CalendarScreen> createState() => _CalendarScreenState();
|
||||
}
|
||||
|
||||
class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
||||
late final ValueNotifier<List<Manga>> _selectedEntries;
|
||||
CalendarFormat _calendarFormat = CalendarFormat.month;
|
||||
RangeSelectionMode _rangeSelectionMode = RangeSelectionMode.toggledOff;
|
||||
final firstDay = DateTime.now();
|
||||
final lastDay = DateTime.now().add(const Duration(days: 1000));
|
||||
DateTime _focusedDay = DateTime.now();
|
||||
DateTime? _selectedDay;
|
||||
DateTime? _rangeStart;
|
||||
DateTime? _rangeEnd;
|
||||
late ItemType? itemType = widget.itemType ?? ItemType.manga;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedDay = _focusedDay;
|
||||
_selectedEntries = ValueNotifier([]);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_selectedEntries.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final locale = ref.watch(l10nLocaleStateProvider);
|
||||
final data = ref.watch(getCalendarStreamProvider(itemType: itemType));
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.calendar)),
|
||||
body: data.when(
|
||||
data: (data) {
|
||||
if (_selectedDay != null) {
|
||||
_selectedEntries.value = _getEntriesForDay(_selectedDay!, data);
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_outlined,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Text(
|
||||
l10n.calendar_info,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SegmentedButton(
|
||||
emptySelectionAllowed: true,
|
||||
showSelectedIcon: false,
|
||||
style: TextButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
),
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: ItemType.manga.index,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(l10n.manga),
|
||||
),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ItemType.anime.index,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(l10n.anime),
|
||||
),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ItemType.novel.index,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(l10n.novel),
|
||||
),
|
||||
),
|
||||
],
|
||||
selected: {itemType?.index},
|
||||
onSelectionChanged: (newSelection) {
|
||||
if (newSelection.isNotEmpty &&
|
||||
newSelection.first != null) {
|
||||
setState(() {
|
||||
itemType =
|
||||
ItemType.values[newSelection.first!];
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TableCalendar(
|
||||
firstDay: firstDay,
|
||||
lastDay: lastDay,
|
||||
focusedDay: _focusedDay,
|
||||
locale: locale.toLanguageTag(),
|
||||
selectedDayPredicate: (day) =>
|
||||
isSameDay(_selectedDay, day),
|
||||
rangeStartDay: _rangeStart,
|
||||
rangeEndDay: _rangeEnd,
|
||||
calendarFormat: _calendarFormat,
|
||||
rangeSelectionMode: _rangeSelectionMode,
|
||||
eventLoader: (day) => _getEntriesForDay(day, data),
|
||||
startingDayOfWeek: StartingDayOfWeek.monday,
|
||||
calendarStyle: CalendarStyle(
|
||||
outsideDaysVisible: true,
|
||||
weekendTextStyle: TextStyle(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
),
|
||||
onDaySelected: (selectedDay, focusedDay) =>
|
||||
_onDaySelected(selectedDay, focusedDay, data),
|
||||
onRangeSelected: (start, end, focusedDay) =>
|
||||
_onRangeSelected(start, end, focusedDay, data),
|
||||
onFormatChanged: (format) {
|
||||
if (_calendarFormat != format) {
|
||||
setState(() {
|
||||
_calendarFormat = format;
|
||||
});
|
||||
}
|
||||
},
|
||||
onPageChanged: (focusedDay) {
|
||||
_focusedDay = focusedDay;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
],
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder<List<Manga>>(
|
||||
valueListenable: _selectedEntries,
|
||||
builder: (context, value, _) {
|
||||
return CustomSliverGroupedListView<Manga, String>(
|
||||
elements: value,
|
||||
groupBy: (element) {
|
||||
return dateFormat(
|
||||
_selectedDay?.millisecondsSinceEpoch.toString() ??
|
||||
DateTime.now()
|
||||
.add(Duration(days: element.smartUpdateDays!))
|
||||
.millisecondsSinceEpoch
|
||||
.toString(),
|
||||
context: context,
|
||||
ref: ref,
|
||||
forHistoryValue: true,
|
||||
useRelativeTimesTamps: false,
|
||||
);
|
||||
},
|
||||
groupSeparatorBuilder: (String groupByValue) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8, left: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"${dateFormat(null, context: context, stringDate: groupByValue, ref: ref, useRelativeTimesTamps: true, showInDaysFuture: true)} - ${dateFormat(null, context: context, stringDate: groupByValue, ref: ref, useRelativeTimesTamps: false)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, element) {
|
||||
return CalendarListTileWidget(
|
||||
manga: element,
|
||||
selectedDay: _selectedDay,
|
||||
);
|
||||
},
|
||||
order: GroupedListOrder.ASC,
|
||||
);
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(child: const SizedBox(height: 15)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (Object error, StackTrace stackTrace) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(l10n.calendar_no_data, textAlign: TextAlign.center),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () {
|
||||
return const ProgressCenter();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Manga> _getEntriesForDay(DateTime day, List<Manga> data) {
|
||||
return data.where((e) {
|
||||
final temp = DateTime.now().add(Duration(days: e.smartUpdateDays!));
|
||||
final predictedDay = "${temp.year}-${temp.month}-${temp.day}";
|
||||
final selectedDay = "${day.year}-${day.month}-${day.day}";
|
||||
return predictedDay == selectedDay;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<Manga> _getEntriesForRange(
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
List<Manga> data,
|
||||
) {
|
||||
final days = _daysInRange(start, end);
|
||||
|
||||
return [for (final d in days) ..._getEntriesForDay(d, data)];
|
||||
}
|
||||
|
||||
void _onDaySelected(
|
||||
DateTime selectedDay,
|
||||
DateTime focusedDay,
|
||||
List<Manga> data,
|
||||
) {
|
||||
if (!isSameDay(_selectedDay, selectedDay)) {
|
||||
setState(() {
|
||||
_selectedDay = selectedDay;
|
||||
_focusedDay = focusedDay;
|
||||
_rangeStart = null;
|
||||
_rangeEnd = null;
|
||||
_rangeSelectionMode = RangeSelectionMode.toggledOff;
|
||||
});
|
||||
|
||||
_selectedEntries.value = _getEntriesForDay(selectedDay, data);
|
||||
}
|
||||
}
|
||||
|
||||
void _onRangeSelected(
|
||||
DateTime? start,
|
||||
DateTime? end,
|
||||
DateTime focusedDay,
|
||||
List<Manga> data,
|
||||
) {
|
||||
setState(() {
|
||||
_selectedDay = null;
|
||||
_focusedDay = focusedDay;
|
||||
_rangeStart = start;
|
||||
_rangeEnd = end;
|
||||
_rangeSelectionMode = RangeSelectionMode.toggledOn;
|
||||
});
|
||||
|
||||
if (start != null && end != null) {
|
||||
_selectedEntries.value = _getEntriesForRange(start, end, data);
|
||||
} else if (start != null) {
|
||||
_selectedEntries.value = _getEntriesForDay(start, data);
|
||||
} else if (end != null) {
|
||||
_selectedEntries.value = _getEntriesForDay(end, data);
|
||||
}
|
||||
}
|
||||
|
||||
List<DateTime> _daysInRange(DateTime first, DateTime last) {
|
||||
final dayCount = last.difference(first).inDays + 1;
|
||||
return List.generate(
|
||||
dayCount,
|
||||
(index) => DateTime.utc(first.year, first.month, first.day + index),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CalendarListTileWidget extends ConsumerWidget {
|
||||
final Manga manga;
|
||||
final DateTime? selectedDay;
|
||||
const CalendarListTileWidget({
|
||||
required this.manga,
|
||||
required this.selectedDay,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: InkWell(
|
||||
onTap: () => context.push('/manga-reader/detail', extra: manga.id),
|
||||
onLongPress: () {},
|
||||
onSecondaryTap: () {},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 5),
|
||||
child: Container(
|
||||
height: 45,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Material(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.push(
|
||||
'/manga-reader/detail',
|
||||
extra: manga.id,
|
||||
);
|
||||
},
|
||||
child: Ink.image(
|
||||
fit: BoxFit.cover,
|
||||
width: 40,
|
||||
height: 45,
|
||||
image: manga.customCoverImage != null
|
||||
? MemoryImage(
|
||||
manga.customCoverImage as Uint8List,
|
||||
)
|
||||
as ImageProvider
|
||||
: CustomExtendedNetworkImageProvider(
|
||||
toImgUrl(
|
||||
manga.customCoverFromTracker ??
|
||||
manga.imageUrl!,
|
||||
),
|
||||
headers: ref.watch(
|
||||
headersProvider(
|
||||
source: manga.source!,
|
||||
lang: manga.lang!,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: InkWell(child: Container()),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
manga.name!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
context.l10n.n_chapters(
|
||||
manga.chapters.countSync(),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
25
lib/modules/calendar/providers/calendar_provider.dart
Normal file
25
lib/modules/calendar/providers/calendar_provider.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
part 'calendar_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Stream<List<Manga>> getCalendarStream(
|
||||
Ref ref, {
|
||||
ItemType? itemType,
|
||||
}) async* {
|
||||
yield* isar.mangas
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.itemTypeEqualTo(itemType ?? ItemType.manga)
|
||||
.anyOf([
|
||||
Status.ongoing,
|
||||
Status.unknown,
|
||||
Status.publishingFinished,
|
||||
], (q, status) => q.statusEqualTo(status))
|
||||
.smartUpdateDaysIsNotNull()
|
||||
.smartUpdateDaysGreaterThan(0)
|
||||
.watch(fireImmediately: true);
|
||||
}
|
||||
161
lib/modules/calendar/providers/calendar_provider.g.dart
Normal file
161
lib/modules/calendar/providers/calendar_provider.g.dart
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'calendar_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getCalendarStreamHash() => r'dcdad165b2da2420bafa8b70c4b3a0fb336e5021';
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [getCalendarStream].
|
||||
@ProviderFor(getCalendarStream)
|
||||
const getCalendarStreamProvider = GetCalendarStreamFamily();
|
||||
|
||||
/// See also [getCalendarStream].
|
||||
class GetCalendarStreamFamily extends Family<AsyncValue<List<Manga>>> {
|
||||
/// See also [getCalendarStream].
|
||||
const GetCalendarStreamFamily();
|
||||
|
||||
/// See also [getCalendarStream].
|
||||
GetCalendarStreamProvider call({
|
||||
ItemType? itemType,
|
||||
}) {
|
||||
return GetCalendarStreamProvider(
|
||||
itemType: itemType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
GetCalendarStreamProvider getProviderOverride(
|
||||
covariant GetCalendarStreamProvider provider,
|
||||
) {
|
||||
return call(
|
||||
itemType: provider.itemType,
|
||||
);
|
||||
}
|
||||
|
||||
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'getCalendarStreamProvider';
|
||||
}
|
||||
|
||||
/// See also [getCalendarStream].
|
||||
class GetCalendarStreamProvider extends AutoDisposeStreamProvider<List<Manga>> {
|
||||
/// See also [getCalendarStream].
|
||||
GetCalendarStreamProvider({
|
||||
ItemType? itemType,
|
||||
}) : this._internal(
|
||||
(ref) => getCalendarStream(
|
||||
ref as GetCalendarStreamRef,
|
||||
itemType: itemType,
|
||||
),
|
||||
from: getCalendarStreamProvider,
|
||||
name: r'getCalendarStreamProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$getCalendarStreamHash,
|
||||
dependencies: GetCalendarStreamFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
GetCalendarStreamFamily._allTransitiveDependencies,
|
||||
itemType: itemType,
|
||||
);
|
||||
|
||||
GetCalendarStreamProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.itemType,
|
||||
}) : super.internal();
|
||||
|
||||
final ItemType? itemType;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<List<Manga>> Function(GetCalendarStreamRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: GetCalendarStreamProvider._internal(
|
||||
(ref) => create(ref as GetCalendarStreamRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
itemType: itemType,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<List<Manga>> createElement() {
|
||||
return _GetCalendarStreamProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is GetCalendarStreamProvider && other.itemType == itemType;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, itemType.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin GetCalendarStreamRef on AutoDisposeStreamProviderRef<List<Manga>> {
|
||||
/// The parameter `itemType` of this provider.
|
||||
ItemType? get itemType;
|
||||
}
|
||||
|
||||
class _GetCalendarStreamProviderElement
|
||||
extends AutoDisposeStreamProviderElement<List<Manga>>
|
||||
with GetCalendarStreamRef {
|
||||
_GetCalendarStreamProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
ItemType? get itemType => (origin as GetCalendarStreamProvider).itemType;
|
||||
}
|
||||
// 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
|
||||
|
|
@ -451,26 +451,21 @@ class _HistoryTabState extends ConsumerState<HistoryTab>
|
|||
) async {
|
||||
await manga.chapters.load();
|
||||
final chapters = manga.chapters;
|
||||
await isar.writeTxn(() async {
|
||||
await isar.historys.delete(deleteId!);
|
||||
isar.writeTxnSync(() {
|
||||
isar.historys.deleteSync(deleteId!);
|
||||
for (var chapter in chapters) {
|
||||
await isar.chapters.delete(chapter.id!);
|
||||
await ref
|
||||
isar.chapters.deleteSync(chapter.id!);
|
||||
ref
|
||||
.read(synchingProvider(syncId: 1).notifier)
|
||||
.addChangedPartAsync(
|
||||
ActionType.removeChapter,
|
||||
chapter.id,
|
||||
"{}",
|
||||
false,
|
||||
);
|
||||
.addChangedPart(ActionType.removeChapter, chapter.id, "{}", false);
|
||||
}
|
||||
await isar.mangas.delete(manga.id!);
|
||||
await ref
|
||||
isar.mangas.deleteSync(manga.id!);
|
||||
ref
|
||||
.read(synchingProvider(syncId: 1).notifier)
|
||||
.addChangedPartAsync(ActionType.removeHistory, deleteId, "{}", false);
|
||||
await ref
|
||||
.addChangedPart(ActionType.removeHistory, deleteId, "{}", false);
|
||||
ref
|
||||
.read(synchingProvider(syncId: 1).notifier)
|
||||
.addChangedPartAsync(ActionType.removeItem, manga.id, "{}", false);
|
||||
.addChangedPart(ActionType.removeItem, manga.id, "{}", false);
|
||||
});
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
|
|
|
|||
|
|
@ -1782,6 +1782,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
child: Row(
|
||||
children: [
|
||||
Expanded(child: widget.action!),
|
||||
Expanded(child: _smartUpdateDays()),
|
||||
Expanded(
|
||||
child: widget.itemType == ItemType.novel
|
||||
? SizedBox.shrink()
|
||||
|
|
@ -1832,6 +1833,36 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
);
|
||||
}
|
||||
|
||||
Widget _smartUpdateDays() {
|
||||
return SizedBox(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
elevation: 0,
|
||||
),
|
||||
onPressed: () =>
|
||||
context.push("/calendarScreen", extra: widget.manga!.itemType),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.hourglass_empty,
|
||||
size: 20,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.manga?.smartUpdateDays != null
|
||||
? context.l10n.n_days(widget.manga!.smartUpdateDays!)
|
||||
: "N/A",
|
||||
style: TextStyle(fontSize: 11, color: context.secondaryColor),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Tracker button
|
||||
Widget _action() {
|
||||
return StreamBuilder(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/eval/model/m_manga.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
|
|
@ -5,6 +7,7 @@ import 'package:mangayomi/models/chapter.dart';
|
|||
import 'package:mangayomi/models/update.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/services/get_detail.dart';
|
||||
import 'package:mangayomi/utils/extensions/others.dart';
|
||||
import 'package:mangayomi/utils/utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -62,7 +65,7 @@ Future<dynamic> updateMangaDetail(
|
|||
return;
|
||||
}
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(manga);
|
||||
final mangaId = isar.mangas.putSync(manga);
|
||||
manga.lastUpdate = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
List<Chapter> chapters = [];
|
||||
|
|
@ -81,6 +84,11 @@ Future<dynamic> updateMangaDetail(
|
|||
scanlator: chaps[i].scanlator ?? '',
|
||||
mangaId: mangaId,
|
||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||
isFiller: chaps[i].isFiller,
|
||||
thumbnailUrl: chaps[i].thumbnailUrl,
|
||||
description: chaps[i].description,
|
||||
downloadSize: chaps[i].downloadSize,
|
||||
duration: chaps[i].duration,
|
||||
)..manga.value = manga;
|
||||
chapters.add(chapter);
|
||||
}
|
||||
|
|
@ -115,10 +123,38 @@ Future<dynamic> updateMangaDetail(
|
|||
oldChap.url = newChap.url;
|
||||
oldChap.scanlator = newChap.scanlator;
|
||||
oldChap.updatedAt = DateTime.now().millisecondsSinceEpoch;
|
||||
oldChap.isFiller = newChap.isFiller;
|
||||
oldChap.thumbnailUrl = newChap.thumbnailUrl;
|
||||
oldChap.description = newChap.description;
|
||||
oldChap.downloadSize = newChap.downloadSize;
|
||||
oldChap.duration = newChap.duration;
|
||||
isar.chapters.putSync(oldChap);
|
||||
oldChap.manga.saveSync();
|
||||
}
|
||||
}
|
||||
final List<int> daysBetweenUploads = [];
|
||||
for (var i = 0; i + 1 < chaps.length; i++) {
|
||||
if (chaps[i].dateUpload != null && chaps[i + 1].dateUpload != null) {
|
||||
final date1 = DateTime.fromMillisecondsSinceEpoch(
|
||||
int.parse(chaps[i].dateUpload!),
|
||||
);
|
||||
final date2 = DateTime.fromMillisecondsSinceEpoch(
|
||||
int.parse(chaps[i + 1].dateUpload!),
|
||||
);
|
||||
daysBetweenUploads.add(date1.difference(date2).abs().inDays);
|
||||
}
|
||||
}
|
||||
if (daysBetweenUploads.isNotEmpty) {
|
||||
final median = daysBetweenUploads.median();
|
||||
isar.mangas.putSync(
|
||||
manga
|
||||
..id = mangaId
|
||||
..smartUpdateDays = max(
|
||||
median,
|
||||
daysBetweenUploads.arithmeticMean(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (e, s) {
|
||||
if (showToast) botToast('$e\n$s');
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'update_manga_detail_providers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$updateMangaDetailHash() => r'ce51918a48b315c3555b3de4e602bd998e00a992';
|
||||
String _$updateMangaDetailHash() => r'85660b206c2bce558760118936758a261519cad8';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/utils/constant.dart';
|
||||
import 'package:marquee/marquee.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
|
|
@ -10,6 +13,8 @@ import 'package:mangayomi/utils/extensions/chapter.dart';
|
|||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
|
||||
import 'package:mangayomi/modules/manga/download/download_page_widget.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
|
||||
class ChapterListTileWidget extends ConsumerWidget {
|
||||
final Chapter chapter;
|
||||
|
|
@ -33,6 +38,9 @@ class ChapterListTileWidget extends ConsumerWidget {
|
|||
onLongPress: () => _handleInteraction(ref),
|
||||
onSecondaryTap: () => _handleInteraction(ref),
|
||||
child: ListTile(
|
||||
tileColor: (chapter.isFiller ?? false)
|
||||
? context.primaryColor.withValues(alpha: 0.15)
|
||||
: null,
|
||||
textColor: chapter.isRead!
|
||||
? context.isLight
|
||||
? Colors.black.withValues(alpha: 0.4)
|
||||
|
|
@ -44,14 +52,43 @@ class ChapterListTileWidget extends ConsumerWidget {
|
|||
onTap: () async => _handleInteraction(ref, context),
|
||||
title: Row(
|
||||
children: [
|
||||
if (chapter.thumbnailUrl != null)
|
||||
_thumbnailPreview(context, chapter.thumbnailUrl),
|
||||
chapter.isBookmarked!
|
||||
? Icon(Icons.bookmark, size: 16, color: context.primaryColor)
|
||||
: Container(),
|
||||
Flexible(child: _buildTitle(chapter.name!, context)),
|
||||
chapter.description != null
|
||||
? Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTitle(chapter.name!, context),
|
||||
Text(
|
||||
chapter.description!,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Flexible(child: _buildTitle(chapter.name!, context)),
|
||||
],
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
if (chapter.isFiller ?? false)
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.label, size: 16, color: context.primaryColor),
|
||||
Text(
|
||||
" Filler ",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if ((chapter.manga.value!.isLocalArchive ?? false) == false)
|
||||
Text(
|
||||
chapter.dateUpload == null || chapter.dateUpload!.isEmpty
|
||||
|
|
@ -172,4 +209,62 @@ class ChapterListTileWidget extends ConsumerWidget {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _thumbnailPreview(BuildContext context, String? imageUrl) {
|
||||
final imageProvider = CustomExtendedNetworkImageProvider(
|
||||
toImgUrl(imageUrl ?? ""),
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_openImage(context, imageProvider);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 50,
|
||||
height: 65,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
image: DecorationImage(image: imageProvider, fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openImage(BuildContext context, ImageProvider imageProvider) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: PhotoViewGallery.builder(
|
||||
backgroundDecoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
itemCount: 1,
|
||||
builder: (context, index) {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
imageProvider: imageProvider,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: 2.0,
|
||||
);
|
||||
},
|
||||
loadingBuilder: (context, event) {
|
||||
return const ProgressCenter();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/image_view_paged.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/transition_view_paged.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:extended_image/extended_image.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/image_view_paged.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/transition_view_vertical.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:extended_image/extended_image.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/utils/extensions/others.dart';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:extended_image/extended_image.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/src/rust/api/image.dart';
|
||||
import 'package:mangayomi/src/rust/frb_generated.dart';
|
||||
import 'package:mangayomi/utils/extensions/others.dart';
|
||||
|
|
|
|||
|
|
@ -12,14 +12,16 @@ import 'package:mangayomi/eval/model/m_bridge.dart';
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/page.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/anime/widgets/desktop.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/crop_borders_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/btn_chapter_list_dialog.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/double_columm_view_center.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/color_filter_provider.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/custom_popup_menu_button.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/custom_value_indicator_shape.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
|
|
@ -1631,7 +1633,7 @@ class _MangaChapterPageGalleryState
|
|||
return SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
valueIndicatorShape:
|
||||
_CustomValueIndicatorShape(
|
||||
CustomValueIndicatorShape(
|
||||
tranform: _isReverseHorizontal,
|
||||
),
|
||||
overlayShape:
|
||||
|
|
@ -2502,190 +2504,3 @@ class _MangaChapterPageGalleryState
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UChapDataPreload {
|
||||
Chapter? chapter;
|
||||
Directory? directory;
|
||||
PageUrl? pageUrl;
|
||||
bool? isLocale;
|
||||
Uint8List? archiveImage;
|
||||
int? index;
|
||||
GetChapterPagesModel? chapterUrlModel;
|
||||
int? pageIndex;
|
||||
Uint8List? cropImage;
|
||||
bool isTransitionPage;
|
||||
Chapter? nextChapter;
|
||||
String? mangaName;
|
||||
bool? isLastChapter;
|
||||
|
||||
UChapDataPreload(
|
||||
this.chapter,
|
||||
this.directory,
|
||||
this.pageUrl,
|
||||
this.isLocale,
|
||||
this.archiveImage,
|
||||
this.index,
|
||||
this.chapterUrlModel,
|
||||
this.pageIndex, {
|
||||
this.cropImage,
|
||||
this.isTransitionPage = false,
|
||||
this.nextChapter,
|
||||
this.mangaName,
|
||||
this.isLastChapter = false,
|
||||
});
|
||||
|
||||
UChapDataPreload.transition({
|
||||
required Chapter currentChapter,
|
||||
required this.nextChapter,
|
||||
required String this.mangaName,
|
||||
required int this.pageIndex,
|
||||
this.isLastChapter = false,
|
||||
}) : chapter = currentChapter,
|
||||
isTransitionPage = true,
|
||||
directory = null,
|
||||
pageUrl = null,
|
||||
isLocale = null,
|
||||
archiveImage = null,
|
||||
index = null,
|
||||
chapterUrlModel = null,
|
||||
cropImage = null;
|
||||
}
|
||||
|
||||
class CustomPopupMenuButton<T> extends StatelessWidget {
|
||||
final String label;
|
||||
final String title;
|
||||
final ValueChanged<T> onSelected;
|
||||
final T value;
|
||||
final List<T> list;
|
||||
final String Function(T) itemText;
|
||||
const CustomPopupMenuButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.title,
|
||||
required this.onSelected,
|
||||
required this.value,
|
||||
required this.list,
|
||||
required this.itemText,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: PopupMenuButton(
|
||||
popUpAnimationStyle: popupAnimationStyle,
|
||||
tooltip: "",
|
||||
offset: Offset.fromDirection(1),
|
||||
color: Colors.black,
|
||||
onSelected: onSelected,
|
||||
itemBuilder: (context) => [
|
||||
for (var d in list)
|
||||
PopupMenuItem(
|
||||
value: d,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: d == value ? Colors.white : Colors.transparent,
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
Text(
|
||||
itemText(d),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(title),
|
||||
const SizedBox(width: 20),
|
||||
const Icon(Icons.keyboard_arrow_down_outlined),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomValueIndicatorShape extends SliderComponentShape {
|
||||
final _indicatorShape = const PaddleSliderValueIndicatorShape();
|
||||
final bool tranform;
|
||||
const _CustomValueIndicatorShape({this.tranform = false});
|
||||
@override
|
||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
|
||||
return const Size(40, 40);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset center, {
|
||||
required Animation<double> activationAnimation,
|
||||
required Animation<double> enableAnimation,
|
||||
required bool isDiscrete,
|
||||
required TextPainter labelPainter,
|
||||
required RenderBox parentBox,
|
||||
required SliderThemeData sliderTheme,
|
||||
required TextDirection textDirection,
|
||||
required double value,
|
||||
required double textScaleFactor,
|
||||
required Size sizeWithOverflow,
|
||||
}) {
|
||||
final textSpan = TextSpan(
|
||||
text: labelPainter.text?.toPlainText(),
|
||||
style: sliderTheme.valueIndicatorTextStyle,
|
||||
);
|
||||
|
||||
final textPainter = TextPainter(
|
||||
text: textSpan,
|
||||
textAlign: labelPainter.textAlign,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
|
||||
textPainter.layout();
|
||||
|
||||
context.canvas.save();
|
||||
context.canvas.translate(center.dx, center.dy);
|
||||
context.canvas.scale(tranform ? -1.0 : 1.0, 1.0);
|
||||
context.canvas.translate(-center.dx, -center.dy);
|
||||
|
||||
_indicatorShape.paint(
|
||||
context,
|
||||
center,
|
||||
activationAnimation: activationAnimation,
|
||||
enableAnimation: enableAnimation,
|
||||
labelPainter: textPainter,
|
||||
parentBox: parentBox,
|
||||
sliderTheme: sliderTheme,
|
||||
value: value,
|
||||
textScaleFactor: textScaleFactor,
|
||||
sizeWithOverflow: sizeWithOverflow,
|
||||
isDiscrete: isDiscrete,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
|
||||
context.canvas.restore();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
54
lib/modules/manga/reader/u_chap_data_preload.dart
Normal file
54
lib/modules/manga/reader/u_chap_data_preload.dart
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/page.dart';
|
||||
import 'package:mangayomi/services/get_chapter_pages.dart';
|
||||
|
||||
class UChapDataPreload {
|
||||
Chapter? chapter;
|
||||
Directory? directory;
|
||||
PageUrl? pageUrl;
|
||||
bool? isLocale;
|
||||
Uint8List? archiveImage;
|
||||
int? index;
|
||||
GetChapterPagesModel? chapterUrlModel;
|
||||
int? pageIndex;
|
||||
Uint8List? cropImage;
|
||||
bool isTransitionPage;
|
||||
Chapter? nextChapter;
|
||||
String? mangaName;
|
||||
bool? isLastChapter;
|
||||
|
||||
UChapDataPreload(
|
||||
this.chapter,
|
||||
this.directory,
|
||||
this.pageUrl,
|
||||
this.isLocale,
|
||||
this.archiveImage,
|
||||
this.index,
|
||||
this.chapterUrlModel,
|
||||
this.pageIndex, {
|
||||
this.cropImage,
|
||||
this.isTransitionPage = false,
|
||||
this.nextChapter,
|
||||
this.mangaName,
|
||||
this.isLastChapter = false,
|
||||
});
|
||||
|
||||
UChapDataPreload.transition({
|
||||
required Chapter currentChapter,
|
||||
required this.nextChapter,
|
||||
required String this.mangaName,
|
||||
required int this.pageIndex,
|
||||
this.isLastChapter = false,
|
||||
}) : chapter = currentChapter,
|
||||
isTransitionPage = true,
|
||||
directory = null,
|
||||
pageUrl = null,
|
||||
isLocale = null,
|
||||
archiveImage = null,
|
||||
index = null,
|
||||
chapterUrlModel = null,
|
||||
cropImage = null;
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart' as reader;
|
||||
import 'package:mangayomi/modules/manga/reader/image_view_vertical.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/double_columm_view_vertical.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/transition_view_vertical.dart';
|
||||
|
|
@ -21,7 +21,7 @@ class VirtualMangaList extends ConsumerStatefulWidget {
|
|||
final double minCacheExtent;
|
||||
final int initialScrollIndex;
|
||||
final ScrollPhysics physics;
|
||||
final Function(reader.UChapDataPreload data) onLongPressData;
|
||||
final Function(UChapDataPreload data) onLongPressData;
|
||||
final Function(bool) onFailedToLoadImage;
|
||||
final BackgroundColor backgroundColor;
|
||||
final bool isDoublePageMode;
|
||||
|
|
@ -215,7 +215,7 @@ class _VirtualMangaListState extends ConsumerState<VirtualMangaList> {
|
|||
final int index1 = index * 2 - 1;
|
||||
final int index2 = index1 + 1;
|
||||
|
||||
final List<reader.UChapDataPreload?> datas = index == 0
|
||||
final List<UChapDataPreload?> datas = index == 0
|
||||
? [widget.pageManager.getOriginalPage(0), null]
|
||||
: [
|
||||
index1 < widget.pageManager.pageCount
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
|||
import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart' as reader;
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
|
||||
/// Page loading states for virtual scrolling
|
||||
enum PageLoadState { notLoaded, loading, loaded, error, cached }
|
||||
|
|
@ -10,7 +10,7 @@ enum PageLoadState { notLoaded, loading, loaded, error, cached }
|
|||
/// Virtual page information for tracking state
|
||||
class VirtualPageInfo {
|
||||
final int index;
|
||||
final reader.UChapDataPreload originalData;
|
||||
final UChapDataPreload originalData;
|
||||
PageLoadState loadState;
|
||||
DateTime? lastAccessTime;
|
||||
Object? error;
|
||||
|
|
@ -56,7 +56,7 @@ class VirtualPageConfig {
|
|||
|
||||
/// Manages virtual page loading and memory optimization
|
||||
class VirtualPageManager extends ChangeNotifier {
|
||||
final List<reader.UChapDataPreload> _originalPages;
|
||||
final List<UChapDataPreload> _originalPages;
|
||||
final VirtualPageConfig config;
|
||||
final Map<int, VirtualPageInfo> _pageInfoMap = {};
|
||||
final Set<int> _preloadQueue = {};
|
||||
|
|
@ -65,7 +65,7 @@ class VirtualPageManager extends ChangeNotifier {
|
|||
Timer? _cleanupTimer;
|
||||
|
||||
VirtualPageManager({
|
||||
required List<reader.UChapDataPreload> pages,
|
||||
required List<UChapDataPreload> pages,
|
||||
this.config = const VirtualPageConfig(),
|
||||
}) : _originalPages = List.from(pages) {
|
||||
_initializePages();
|
||||
|
|
@ -108,7 +108,7 @@ class VirtualPageManager extends ChangeNotifier {
|
|||
}
|
||||
|
||||
/// Get original page data
|
||||
reader.UChapDataPreload? getOriginalPage(int index) {
|
||||
UChapDataPreload? getOriginalPage(int index) {
|
||||
if (index < 0 || index >= _originalPages.length) return null;
|
||||
return _originalPages[index];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
|
@ -7,11 +8,10 @@ import 'package:mangayomi/models/settings.dart';
|
|||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart' as reader;
|
||||
|
||||
/// Provides virtual page manager instances
|
||||
final virtualPageManagerProvider =
|
||||
Provider.family<VirtualPageManager, List<reader.UChapDataPreload>>((
|
||||
Provider.family<VirtualPageManager, List<UChapDataPreload>>((
|
||||
ref,
|
||||
pages,
|
||||
) {
|
||||
|
|
@ -20,7 +20,7 @@ final virtualPageManagerProvider =
|
|||
|
||||
/// Main widget for virtual reading that replaces ScrollablePositionedList
|
||||
class VirtualReaderView extends ConsumerStatefulWidget {
|
||||
final List<reader.UChapDataPreload> pages;
|
||||
final List<UChapDataPreload> pages;
|
||||
final ItemScrollController itemScrollController;
|
||||
final ScrollOffsetController scrollOffsetController;
|
||||
final ItemPositionsListener itemPositionsListener;
|
||||
|
|
@ -28,7 +28,7 @@ class VirtualReaderView extends ConsumerStatefulWidget {
|
|||
final double minCacheExtent;
|
||||
final int initialScrollIndex;
|
||||
final ScrollPhysics physics;
|
||||
final Function(reader.UChapDataPreload data) onLongPressData;
|
||||
final Function(UChapDataPreload data) onLongPressData;
|
||||
final Function(bool) onFailedToLoadImage;
|
||||
final BackgroundColor backgroundColor;
|
||||
final bool isDoublePageMode;
|
||||
|
|
@ -169,10 +169,10 @@ mixin VirtualPageManagerMixin<T extends ConsumerStatefulWidget>
|
|||
}
|
||||
|
||||
/// Override this method to provide the pages list
|
||||
List<reader.UChapDataPreload> getPages();
|
||||
List<UChapDataPreload> getPages();
|
||||
|
||||
/// Call this when pages change
|
||||
void updateVirtualPages(List<reader.UChapDataPreload> newPages) {
|
||||
void updateVirtualPages(List<UChapDataPreload> newPages) {
|
||||
_virtualPageManager?.dispose();
|
||||
_virtualPageManager = VirtualPageManager(pages: newPages);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/utils/global_style.dart';
|
||||
|
||||
class CustomPopupMenuButton<T> extends StatelessWidget {
|
||||
final String label;
|
||||
final String title;
|
||||
final ValueChanged<T> onSelected;
|
||||
final T value;
|
||||
final List<T> list;
|
||||
final String Function(T) itemText;
|
||||
const CustomPopupMenuButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.title,
|
||||
required this.onSelected,
|
||||
required this.value,
|
||||
required this.list,
|
||||
required this.itemText,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: PopupMenuButton(
|
||||
popUpAnimationStyle: popupAnimationStyle,
|
||||
tooltip: "",
|
||||
offset: Offset.fromDirection(1),
|
||||
color: Colors.black,
|
||||
onSelected: onSelected,
|
||||
itemBuilder: (context) => [
|
||||
for (var d in list)
|
||||
PopupMenuItem(
|
||||
value: d,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: d == value ? Colors.white : Colors.transparent,
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
Text(
|
||||
itemText(d),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(title),
|
||||
const SizedBox(width: 20),
|
||||
const Icon(Icons.keyboard_arrow_down_outlined),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomValueIndicatorShape extends SliderComponentShape {
|
||||
final _indicatorShape = const PaddleSliderValueIndicatorShape();
|
||||
final bool tranform;
|
||||
const CustomValueIndicatorShape({this.tranform = false});
|
||||
@override
|
||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
|
||||
return const Size(40, 40);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset center, {
|
||||
required Animation<double> activationAnimation,
|
||||
required Animation<double> enableAnimation,
|
||||
required bool isDiscrete,
|
||||
required TextPainter labelPainter,
|
||||
required RenderBox parentBox,
|
||||
required SliderThemeData sliderTheme,
|
||||
required TextDirection textDirection,
|
||||
required double value,
|
||||
required double textScaleFactor,
|
||||
required Size sizeWithOverflow,
|
||||
}) {
|
||||
final textSpan = TextSpan(
|
||||
text: labelPainter.text?.toPlainText(),
|
||||
style: sliderTheme.valueIndicatorTextStyle,
|
||||
);
|
||||
|
||||
final textPainter = TextPainter(
|
||||
text: textSpan,
|
||||
textAlign: labelPainter.textAlign,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
|
||||
textPainter.layout();
|
||||
|
||||
context.canvas.save();
|
||||
context.canvas.translate(center.dx, center.dy);
|
||||
context.canvas.scale(tranform ? -1.0 : 1.0, 1.0);
|
||||
context.canvas.translate(-center.dx, -center.dy);
|
||||
|
||||
_indicatorShape.paint(
|
||||
context,
|
||||
center,
|
||||
activationAnimation: activationAnimation,
|
||||
enableAnimation: enableAnimation,
|
||||
labelPainter: textPainter,
|
||||
parentBox: parentBox,
|
||||
sliderTheme: sliderTheme,
|
||||
value: value,
|
||||
textScaleFactor: textScaleFactor,
|
||||
sizeWithOverflow: sizeWithOverflow,
|
||||
isDiscrete: isDiscrete,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
|
||||
context.canvas.restore();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/chapter_transition_page.dart';
|
||||
|
||||
class TransitionViewPaged extends ConsumerWidget {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/chapter_transition_page.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ class DownloadFileScreen extends ConsumerStatefulWidget {
|
|||
class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
|
||||
int _total = 0;
|
||||
int _received = 0;
|
||||
late http.StreamedResponse _response;
|
||||
http.StreamedResponse? _response;
|
||||
final List<int> _bytes = [];
|
||||
late StreamSubscription<List<int>>? _subscription;
|
||||
StreamSubscription<List<int>>? _subscription;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
|
@ -131,8 +131,8 @@ class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
|
|||
return;
|
||||
}
|
||||
_response = await http.Client().send(http.Request('GET', Uri.parse(url)));
|
||||
_total = _response.contentLength ?? 0;
|
||||
_subscription = _response.stream.listen((value) {
|
||||
_total = _response?.contentLength ?? 0;
|
||||
_subscription = _response?.stream.listen((value) {
|
||||
setState(() {
|
||||
_bytes.addAll(value);
|
||||
_received += value.length;
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ List<(String, int)> _getSettingsList(BuildContext context) {
|
|||
final l10n = context.l10n;
|
||||
return [
|
||||
(l10n.app_settings, 6),
|
||||
(l10n.custom_buttons, 10),
|
||||
(l10n.sources_settings, 7),
|
||||
(l10n.include_sensitive_settings, 8),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class BackupFrequencyOptionsState extends _$BackupFrequencyOptionsState {
|
|||
@override
|
||||
List<int> build() {
|
||||
return isar.settings.getSync(227)!.backupListOptions ??
|
||||
[0, 1, 2, 3, 4, 5, 6, 7];
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 10];
|
||||
}
|
||||
|
||||
void set(List<int> values) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ final backupFrequencyStateProvider =
|
|||
|
||||
typedef _$BackupFrequencyState = AutoDisposeNotifier<int>;
|
||||
String _$backupFrequencyOptionsStateHash() =>
|
||||
r'477541f3b59fe662ea3471400ff62066ea7e2196';
|
||||
r'9aa31bef65e0e2f20b306ed17ff058df2f24a635';
|
||||
|
||||
/// See also [BackupFrequencyOptionsState].
|
||||
@ProviderFor(BackupFrequencyOptionsState)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:mangayomi/eval/model/source_preference.dart';
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/category.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/custom_button.dart';
|
||||
import 'package:mangayomi/models/download.dart';
|
||||
import 'package:mangayomi/models/history.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
|
|
@ -131,6 +132,15 @@ Future<void> doBackUp(
|
|||
.toList();
|
||||
datas.addAll({"extensions": res});
|
||||
}
|
||||
if (list.contains(10)) {
|
||||
final res = isar.customButtons
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"customButtons": res});
|
||||
}
|
||||
final regExp = RegExp(r'[^a-zA-Z0-9 .()\-\s]');
|
||||
final name =
|
||||
'mangayomi_${DateTime.now().toString().replaceAll(regExp, '_').replaceAll(' ', '_')}';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'backup.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$doBackUpHash() => r'ad907e7ff4cd9f05bb3fa2da0fd1a1f1d2c23258';
|
||||
String _$doBackUpHash() => r'a570fe00421ee352ea170440ce27b258cf9ddc15';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:mangayomi/eval/model/source_preference.dart';
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/category.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/custom_button.dart';
|
||||
import 'package:mangayomi/models/download.dart';
|
||||
import 'package:mangayomi/models/update.dart';
|
||||
import 'package:mangayomi/models/history.dart';
|
||||
|
|
@ -155,6 +156,9 @@ void restoreBackup(Ref ref, Map<String, dynamic> backup, {bool full = true}) {
|
|||
final updates = (backup["updates"] as List?)
|
||||
?.map((e) => Update.fromJson(e))
|
||||
.toList();
|
||||
final customButtons = (backup["customButtons"] as List?)
|
||||
?.map((e) => CustomButton.fromJson(e))
|
||||
.toList();
|
||||
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.clearSync();
|
||||
|
|
@ -245,8 +249,11 @@ void restoreBackup(Ref ref, Map<String, dynamic> backup, {bool full = true}) {
|
|||
if (settings != null) {
|
||||
isar.settings.putAllSync(settings);
|
||||
}
|
||||
isar.customButtons.clearSync();
|
||||
if (customButtons != null) {
|
||||
isar.customButtons.putAllSync(customButtons);
|
||||
}
|
||||
_invalidateCommonState(ref);
|
||||
ref.read(routerCurrentLocationStateProvider.notifier).refresh();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
@ -532,6 +539,7 @@ void _invalidateCommonState(Ref ref) {
|
|||
ref.invalidate(extensionsRepoStateProvider(ItemType.manga));
|
||||
ref.invalidate(extensionsRepoStateProvider(ItemType.anime));
|
||||
ref.invalidate(extensionsRepoStateProvider(ItemType.novel));
|
||||
ref.read(routerCurrentLocationStateProvider.notifier).refresh();
|
||||
}
|
||||
|
||||
Status _convertStatusFromTachiBk(int idx) {
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ class _DoRestoreProviderElement extends AutoDisposeProviderElement<void>
|
|||
BuildContext get context => (origin as DoRestoreProvider).context;
|
||||
}
|
||||
|
||||
String _$restoreBackupHash() => r'2f440cfc5359e15bfd3887d27b38ecaf881404c6';
|
||||
String _$restoreBackupHash() => r'adc705e1e65dedcc919a525d7bd14f6451620c43';
|
||||
|
||||
/// See also [restoreBackup].
|
||||
@ProviderFor(restoreBackup)
|
||||
|
|
|
|||
|
|
@ -78,6 +78,13 @@ class MoreScreenState extends ConsumerState<MoreScreen> {
|
|||
icon: Icons.query_stats_outlined,
|
||||
title: l10n.statistics,
|
||||
),
|
||||
ListTileWidget(
|
||||
onTap: () {
|
||||
context.push('/calendarScreen');
|
||||
},
|
||||
icon: Icons.calendar_month_outlined,
|
||||
title: l10n.calendar,
|
||||
),
|
||||
ListTileWidget(
|
||||
onTap: () {
|
||||
context.push('/dataAndStorage');
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'package:mangayomi/models/history.dart';
|
|||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/models/update.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/custom_button_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
|
@ -146,6 +147,17 @@ class BrowseSScreen extends ConsumerWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _showClearLibraryDialog(context, ref),
|
||||
title: Text(l10n.clear_library),
|
||||
subtitle: Text(
|
||||
l10n.clear_library_desc,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (checkForExtensionUpdates)
|
||||
SwitchListTile(
|
||||
value: autoUpdateExtensions,
|
||||
|
|
@ -332,3 +344,146 @@ void _showCleanNonLibraryDialog(BuildContext context, dynamic l10n) {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showClearLibraryDialog(BuildContext context, WidgetRef ref) {
|
||||
final itemTypes = ItemType.values.map((e) => e.name).toList();
|
||||
bool isInputError = true;
|
||||
final textController = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Column(
|
||||
children: [
|
||||
Text(context.l10n.clear_library),
|
||||
Text(
|
||||
context.l10n.clear_library_input,
|
||||
style: TextStyle(fontSize: 11, color: context.secondaryColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: context.width(0.8),
|
||||
child: CustomTextFormField(
|
||||
controller: textController,
|
||||
context: context,
|
||||
isMissing: isInputError,
|
||||
val: (text) => setState(() {
|
||||
isInputError =
|
||||
text.trim().isEmpty ||
|
||||
text.split(",").any((e) => !itemTypes.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: isInputError
|
||||
? null
|
||||
: () {
|
||||
final mangasList = isar.mangas
|
||||
.filter()
|
||||
.anyOf(
|
||||
textController.text
|
||||
.split(",")
|
||||
.map(
|
||||
(e) => switch (e) {
|
||||
"manga" => ItemType.manga,
|
||||
"anime" => ItemType.anime,
|
||||
"novel" => ItemType.novel,
|
||||
_ => null,
|
||||
},
|
||||
),
|
||||
(q, element) => element == null
|
||||
? q.idIsNull()
|
||||
: q.itemTypeEqualTo(element),
|
||||
)
|
||||
.findAllSync();
|
||||
final provider = ref.read(
|
||||
synchingProvider(syncId: 1).notifier,
|
||||
);
|
||||
isar.writeTxnSync(() {
|
||||
for (var manga in mangasList) {
|
||||
final histories = isar.historys
|
||||
.filter()
|
||||
.mangaIdEqualTo(manga.id)
|
||||
.findAllSync();
|
||||
for (var history in histories) {
|
||||
isar.historys.deleteSync(history.id!);
|
||||
provider.addChangedPart(
|
||||
ActionType.removeHistory,
|
||||
history.id,
|
||||
"{}",
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
for (var chapter in manga.chapters) {
|
||||
final updates = isar.updates
|
||||
.filter()
|
||||
.mangaIdEqualTo(chapter.mangaId)
|
||||
.chapterNameEqualTo(chapter.name)
|
||||
.findAllSync();
|
||||
for (var update in updates) {
|
||||
isar.updates.deleteSync(update.id!);
|
||||
provider.addChangedPart(
|
||||
ActionType.removeUpdate,
|
||||
update.id,
|
||||
"{}",
|
||||
false,
|
||||
);
|
||||
}
|
||||
isar.chapters.deleteSync(chapter.id!);
|
||||
provider.addChangedPart(
|
||||
ActionType.removeChapter,
|
||||
chapter.id,
|
||||
"{}",
|
||||
false,
|
||||
);
|
||||
}
|
||||
isar.mangas.deleteSync(manga.id!);
|
||||
provider.addChangedPart(
|
||||
ActionType.removeItem,
|
||||
manga.id,
|
||||
"{}",
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
botToast(
|
||||
context.l10n.cleaned_database(mangasList.length),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
context.l10n.ok,
|
||||
style: TextStyle(
|
||||
color: isInputError
|
||||
? context.secondaryColor
|
||||
: context.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
416
lib/modules/more/settings/player/custom_button_screen.dart
Normal file
416
lib/modules/more/settings/player/custom_button_screen.dart
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/custom_button.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/custom_buttons_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
class CustomButtonScreen extends ConsumerStatefulWidget {
|
||||
const CustomButtonScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<CustomButtonScreen> createState() => _CustomButtonScreenState();
|
||||
}
|
||||
|
||||
class _CustomButtonScreenState extends ConsumerState<CustomButtonScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final customButtons = ref.watch(getCustomButtonsStreamProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.custom_buttons_edit)),
|
||||
body: customButtons.when(
|
||||
data: (data) {
|
||||
if (data.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
l10n.custom_buttons_edit,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, index) {
|
||||
final customButton = data[index];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
key: Key("custom_btn_col_${customButton.id}"),
|
||||
children: [
|
||||
Row(
|
||||
key: Key("custom_btn_row_${customButton.id}"),
|
||||
children: [
|
||||
ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
key: Key("custom_btn_row1_${customButton.id}"),
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
key: Key(
|
||||
"custom_btn_tile_${customButton.id}",
|
||||
),
|
||||
dense: true,
|
||||
title: Text(
|
||||
customButton.title!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
for (final button in data) {
|
||||
button.isFavourite =
|
||||
button.id == customButton.id;
|
||||
}
|
||||
await isar.writeTxn(
|
||||
() async =>
|
||||
await isar.customButtons.putAll(data),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
(customButton.isFavourite ?? false)
|
||||
? Icons.star
|
||||
: Icons.star_border,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await _showEditForm(customButton);
|
||||
},
|
||||
icon: Icon(Icons.mode_edit_outlined),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await _showDeleteButton(customButton);
|
||||
},
|
||||
icon: Icon(Icons.delete_outline),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
customButton.codePress ?? "",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
onReorder: (oldIndex, newIndex) async {
|
||||
if (oldIndex < newIndex) {
|
||||
final draggedItemPos = data[oldIndex].pos;
|
||||
for (var i = oldIndex; i < newIndex - 1; i++) {
|
||||
data[i].pos = data[i + 1].pos;
|
||||
}
|
||||
data[newIndex - 1].pos = draggedItemPos;
|
||||
} else {
|
||||
final draggedItemPos = data[oldIndex].pos;
|
||||
for (var i = oldIndex; i > newIndex; i--) {
|
||||
data[i].pos = data[i - 1].pos;
|
||||
}
|
||||
data[newIndex].pos = draggedItemPos;
|
||||
}
|
||||
await isar.writeTxn(
|
||||
() async => await isar.customButtons.putAll(data),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (Object error, StackTrace stackTrace) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
l10n.custom_buttons_edit,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () {
|
||||
return const ProgressCenter();
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
await _showEditForm(null);
|
||||
},
|
||||
label: Row(
|
||||
children: [
|
||||
const Icon(Icons.add),
|
||||
const SizedBox(width: 10),
|
||||
Text(l10n.add),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showEditForm(CustomButton? customButton) async {
|
||||
bool isTitleMissing = customButton == null;
|
||||
bool isCodePressMissing = customButton == null;
|
||||
final titleController = TextEditingController(
|
||||
text: customButton?.title ?? "",
|
||||
);
|
||||
final codePressController = TextEditingController(
|
||||
text: customButton?.codePress ?? "",
|
||||
);
|
||||
final codeLongPressController = TextEditingController(
|
||||
text: customButton?.codeLongPress ?? "",
|
||||
);
|
||||
final codeStartupController = TextEditingController(
|
||||
text: customButton?.codeStartup ?? "",
|
||||
);
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SizedBox(
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
"${context.l10n.custom_buttons_add}${customButton != null ? " (ID: ${customButton.id})" : ""}",
|
||||
),
|
||||
scrollable: true,
|
||||
content: Column(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
CustomTextFormField(
|
||||
name: context.l10n.custom_buttons_text,
|
||||
helperText: context.l10n.custom_buttons_text_req,
|
||||
allowEnterNewLine: false,
|
||||
controller: titleController,
|
||||
context: context,
|
||||
missing: (value) {
|
||||
setState(() {
|
||||
isTitleMissing = value;
|
||||
});
|
||||
},
|
||||
isMissing: isTitleMissing,
|
||||
val: (val) {},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomTextFormField(
|
||||
name: context.l10n.custom_buttons_js_code,
|
||||
helperText: context.l10n.custom_buttons_js_code_req,
|
||||
minLines: 4,
|
||||
controller: codePressController,
|
||||
context: context,
|
||||
missing: (value) {
|
||||
setState(() {
|
||||
isCodePressMissing = value;
|
||||
});
|
||||
},
|
||||
isMissing: isCodePressMissing,
|
||||
val: (val) {},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomTextFormField(
|
||||
name: context.l10n.custom_buttons_js_code_long,
|
||||
minLines: 4,
|
||||
controller: codeLongPressController,
|
||||
context: context,
|
||||
missing: (value) {},
|
||||
isMissing: false,
|
||||
val: (val) {},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomTextFormField(
|
||||
name: context.l10n.custom_buttons_startup,
|
||||
minLines: 4,
|
||||
controller: codeStartupController,
|
||||
context: context,
|
||||
missing: (value) {},
|
||||
isMissing: false,
|
||||
val: (val) {},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(context.l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: isTitleMissing || isCodePressMissing
|
||||
? null
|
||||
: () async {
|
||||
final temp = await isar.customButtons
|
||||
.filter()
|
||||
.idEqualTo(customButton?.id)
|
||||
.findFirst();
|
||||
final button =
|
||||
temp ??
|
||||
CustomButton(
|
||||
title: "",
|
||||
codePress: "",
|
||||
codeLongPress: "",
|
||||
codeStartup: "",
|
||||
pos: await isar.customButtons.count(),
|
||||
);
|
||||
await isar.writeTxn(() async {
|
||||
await isar.customButtons.put(
|
||||
button
|
||||
..title = titleController.text
|
||||
..codePress = codePressController.text
|
||||
..codeLongPress =
|
||||
codeLongPressController.text
|
||||
..codeStartup =
|
||||
codeStartupController.text,
|
||||
);
|
||||
});
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
customButton == null
|
||||
? context.l10n.add
|
||||
: context.l10n.edit,
|
||||
style: TextStyle(
|
||||
color: isTitleMissing || isCodePressMissing
|
||||
? Theme.of(
|
||||
context,
|
||||
).primaryColor.withValues(alpha: 0.2)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showDeleteButton(CustomButton customButton) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: Text(context.l10n.custom_buttons_delete),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await isar.writeTxn(
|
||||
() async =>
|
||||
await isar.customButtons.delete(customButton.id!),
|
||||
);
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, "ok");
|
||||
}
|
||||
},
|
||||
child: Text(context.l10n.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTextFormField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final BuildContext context;
|
||||
final Function(bool) missing;
|
||||
final bool isMissing;
|
||||
final String name;
|
||||
final String helperText;
|
||||
final int minLines;
|
||||
final bool allowEnterNewLine;
|
||||
final Function(String) val;
|
||||
const CustomTextFormField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.context,
|
||||
required this.missing,
|
||||
required this.isMissing,
|
||||
this.name = "",
|
||||
this.helperText = "",
|
||||
this.minLines = 1,
|
||||
this.allowEnterNewLine = true,
|
||||
required this.val,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
minLines: minLines,
|
||||
maxLines: null,
|
||||
controller: controller,
|
||||
keyboardType: allowEnterNewLine
|
||||
? TextInputType.multiline
|
||||
: TextInputType.text,
|
||||
onChanged: (value) {
|
||||
missing(controller.text.isEmpty);
|
||||
val(value);
|
||||
},
|
||||
onFieldSubmitted: (s) {},
|
||||
decoration: InputDecoration(
|
||||
helperText: helperText,
|
||||
helperStyle: TextStyle(color: isMissing ? Colors.red : null),
|
||||
isDense: true,
|
||||
label: Text(
|
||||
name,
|
||||
style: TextStyle(color: isMissing ? Colors.red : null),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.transparent,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: isMissing ? Colors.red : Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: isMissing ? Colors.red : Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: isMissing ? Colors.red : Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
137
lib/modules/more/settings/player/player_advanced_screen.dart
Normal file
137
lib/modules/more/settings/player/player_advanced_screen.dart
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
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;
|
||||
|
||||
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 {
|
||||
final provider = StorageProvider();
|
||||
if (!(await provider.requestPermission())) {
|
||||
return false;
|
||||
}
|
||||
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") ||
|
||||
file.name.endsWith(".lua"))) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
236
lib/modules/more/settings/player/player_audio_screen.dart
Normal file
236
lib/modules/more/settings/player/player_audio_screen.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
195
lib/modules/more/settings/player/player_decoder_screen.dart
Normal file
195
lib/modules/more/settings/player/player_decoder_screen.dart
Normal 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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
52
lib/modules/more/settings/player/player_overview_screen.dart
Normal file
52
lib/modules/more/settings/player/player_overview_screen.dart
Normal 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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||
|
|
@ -8,11 +10,16 @@ import 'package:numberpicker/numberpicker.dart';
|
|||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
import 'package:mangayomi/l10n/generated/app_localizations.dart';
|
||||
|
||||
class PlayerScreen extends ConsumerWidget {
|
||||
class PlayerScreen extends ConsumerStatefulWidget {
|
||||
const PlayerScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ConsumerState<PlayerScreen> createState() => _PlayerScreenState();
|
||||
}
|
||||
|
||||
class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final defaultSubtitleLang = ref.watch(defaultSubtitleLangStateProvider);
|
||||
final markEpisodeAsSeenType = ref.watch(markEpisodeAsSeenTypeStateProvider);
|
||||
final defaultSkipIntroLength = ref.watch(
|
||||
|
|
@ -26,11 +33,10 @@ class PlayerScreen extends ConsumerWidget {
|
|||
final enableAutoSkip = ref.watch(enableAutoSkipStateProvider);
|
||||
final aniSkipTimeoutLength = ref.watch(aniSkipTimeoutLengthStateProvider);
|
||||
final useLibass = ref.watch(useLibassStateProvider);
|
||||
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: [
|
||||
|
|
@ -343,23 +349,6 @@ class PlayerScreen extends ConsumerWidget {
|
|||
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),
|
||||
|
|
@ -380,6 +369,26 @@ class PlayerScreen extends ConsumerWidget {
|
|||
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),
|
||||
|
|
@ -468,80 +477,6 @@ class PlayerScreen extends ConsumerWidget {
|
|||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/custom_button.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
part 'custom_buttons_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Stream<List<CustomButton>> getCustomButtonsStream(Ref ref) async* {
|
||||
yield* isar.customButtons.filter().idIsNotNull().sortByPos().watch(
|
||||
fireImmediately: true,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'custom_buttons_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getCustomButtonsStreamHash() =>
|
||||
r'476c26eb3d20e9e9eed2e1d8bb15fa74ce357ba3';
|
||||
|
||||
/// See also [getCustomButtonsStream].
|
||||
@ProviderFor(getCustomButtonsStream)
|
||||
final getCustomButtonsStreamProvider =
|
||||
AutoDisposeStreamProvider<List<CustomButton>>.internal(
|
||||
getCustomButtonsStream,
|
||||
name: r'getCustomButtonsStreamProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$getCustomButtonsStreamHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef GetCustomButtonsStreamRef
|
||||
= AutoDisposeStreamProviderRef<List<CustomButton>>;
|
||||
// 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
|
||||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:mangayomi/main.dart';
|
||||
|
|
@ -218,45 +217,18 @@ class UseLibassState extends _$UseLibassState {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
class UseMpvConfigState extends _$UseMpvConfigState {
|
||||
@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;
|
||||
bool build() {
|
||||
return isar.settings.getSync(227)!.useMpvConfig ?? false;
|
||||
}
|
||||
|
||||
void set(String value) {
|
||||
void set(bool value) {
|
||||
final settings = isar.settings.getSync(227);
|
||||
state = value;
|
||||
isar.writeTxnSync(
|
||||
() => isar.settings.putSync(
|
||||
settings!
|
||||
..hwdecMode = value
|
||||
..updatedAt = DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
() => isar.settings.putSync(settings!..useMpvConfig = value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,169 +175,21 @@ final useLibassStateProvider =
|
|||
);
|
||||
|
||||
typedef _$UseLibassState = AutoDisposeNotifier<bool>;
|
||||
String _$hwdecModeStateHash() => r'8186e3c5f3db0e952f629d56b2e580e546aed65e';
|
||||
String _$useMpvConfigStateHash() => r'f91e6a7dbd3c5f7674ba74842521ecfca01c78b0';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
/// See also [UseMpvConfigState].
|
||||
@ProviderFor(UseMpvConfigState)
|
||||
final useMpvConfigStateProvider =
|
||||
AutoDisposeNotifierProvider<UseMpvConfigState, bool>.internal(
|
||||
UseMpvConfigState.new,
|
||||
name: r'useMpvConfigStateProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$useMpvConfigStateHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
typedef _$UseMpvConfigState = 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
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ class ListTileWidget extends StatelessWidget {
|
|||
child: Icon(icon, color: context.primaryColor),
|
||||
),
|
||||
title: Text(title),
|
||||
subtitle: subtitle != null
|
||||
? Text(
|
||||
subtitle!,
|
||||
style: TextStyle(fontSize: 11, color: context.secondaryColor),
|
||||
)
|
||||
: null,
|
||||
trailing: trailing,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/page.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/anime/widgets/desktop.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/btn_chapter_list_dialog.dart';
|
||||
|
|
@ -22,9 +21,7 @@ import 'package:mangayomi/services/get_html_content.dart';
|
|||
import 'package:mangayomi/utils/extensions/dom_extensions.dart';
|
||||
import 'package:mangayomi/utils/utils.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
||||
import 'package:mangayomi/services/get_chapter_pages.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/utils/global_style.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||
|
|
@ -759,104 +756,3 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class UChapDataPreload {
|
||||
Chapter? chapter;
|
||||
Directory? directory;
|
||||
PageUrl? pageUrl;
|
||||
bool? isLocale;
|
||||
Uint8List? archiveImage;
|
||||
int? index;
|
||||
GetChapterPagesModel? chapterUrlModel;
|
||||
int? pageIndex;
|
||||
Uint8List? cropImage;
|
||||
UChapDataPreload(
|
||||
this.chapter,
|
||||
this.directory,
|
||||
this.pageUrl,
|
||||
this.isLocale,
|
||||
this.archiveImage,
|
||||
this.index,
|
||||
this.chapterUrlModel,
|
||||
this.pageIndex, {
|
||||
this.cropImage,
|
||||
});
|
||||
}
|
||||
|
||||
class CustomPopupMenuButton<T> extends StatelessWidget {
|
||||
final String label;
|
||||
final String title;
|
||||
final ValueChanged<T> onSelected;
|
||||
final T value;
|
||||
final List<T> list;
|
||||
final String Function(T) itemText;
|
||||
const CustomPopupMenuButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.title,
|
||||
required this.onSelected,
|
||||
required this.value,
|
||||
required this.list,
|
||||
required this.itemText,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: PopupMenuButton(
|
||||
popUpAnimationStyle: popupAnimationStyle,
|
||||
tooltip: "",
|
||||
offset: Offset.fromDirection(1),
|
||||
color: Colors.black,
|
||||
onSelected: onSelected,
|
||||
itemBuilder: (context) => [
|
||||
for (var d in list)
|
||||
PopupMenuItem(
|
||||
value: d,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: d == value ? Colors.white : Colors.transparent,
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
Text(
|
||||
itemText(d),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(title),
|
||||
const SizedBox(width: 20),
|
||||
const Icon(Icons.keyboard_arrow_down_outlined),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:mangayomi/main.dart';
|
|||
import 'package:mangayomi/models/category.dart';
|
||||
import 'package:mangayomi/models/changed.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/custom_button.dart';
|
||||
import 'package:mangayomi/models/download.dart';
|
||||
import 'package:mangayomi/models/update.dart';
|
||||
import 'package:mangayomi/models/history.dart';
|
||||
|
|
@ -61,6 +62,13 @@ class StorageProvider {
|
|||
return directory;
|
||||
}
|
||||
|
||||
Future<Directory?> getMpvDirectory() async {
|
||||
final defaultDirectory = await getDefaultDirectory();
|
||||
String dbDir = path.join(defaultDirectory!.path, 'mpv');
|
||||
await Directory(dbDir).create(recursive: true);
|
||||
return Directory(dbDir);
|
||||
}
|
||||
|
||||
Future<Directory?> getBtDirectory() async {
|
||||
final gefaultDirectory = await getDefaultDirectory();
|
||||
String dbDir = path.join(gefaultDirectory!.path, 'torrents');
|
||||
|
|
@ -169,6 +177,7 @@ class StorageProvider {
|
|||
ChangedPartSchema,
|
||||
ChapterSchema,
|
||||
CategorySchema,
|
||||
CustomButtonSchema,
|
||||
UpdateSchema,
|
||||
HistorySchema,
|
||||
DownloadSchema,
|
||||
|
|
@ -192,6 +201,43 @@ class StorageProvider {
|
|||
});
|
||||
}
|
||||
|
||||
final customButton = await isar.customButtons
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findFirst();
|
||||
if (customButton == null) {
|
||||
await isar.writeTxn(() async {
|
||||
await isar.customButtons.put(
|
||||
CustomButton(
|
||||
title: "+85 s",
|
||||
codePress:
|
||||
"""local intro_length = mp.get_property_native("user-data/current-anime/intro-length")
|
||||
aniyomi.right_seek_by(intro_length)""",
|
||||
codeLongPress:
|
||||
"""aniyomi.int_picker("Change intro length", "%ds", 0, 255, 1, "user-data/current-anime/intro-length")""",
|
||||
codeStartup: """function update_button(_, length)
|
||||
if length ~= nil then
|
||||
if length == 0 then
|
||||
aniyomi.hide_button()
|
||||
return
|
||||
else
|
||||
aniyomi.show_button()
|
||||
end
|
||||
aniyomi.set_button_title("+" .. length .. " s")
|
||||
end
|
||||
end
|
||||
|
||||
if \$isPrimary then
|
||||
mp.observe_property("user-data/current-anime/intro-length", "number", update_button)
|
||||
end""",
|
||||
isFavourite: true,
|
||||
pos: 0,
|
||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return isar;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,12 +12,18 @@ import 'package:mangayomi/modules/browse/extension/edit_code.dart';
|
|||
import 'package:mangayomi/modules/browse/extension/extension_detail.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/widgets/create_extension.dart';
|
||||
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
|
||||
import 'package:mangayomi/modules/calendar/calendar_screen.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/widgets/migrate_screen.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/widgets/recommendation_screen.dart';
|
||||
import 'package:mangayomi/modules/more/data_and_storage/create_backup.dart';
|
||||
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';
|
||||
|
|
@ -205,6 +211,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",
|
||||
|
|
@ -216,6 +223,23 @@ class RouterNotifier extends ChangeNotifier {
|
|||
name: "customNavigationSettings",
|
||||
child: const CustomNavigationSettings(),
|
||||
),
|
||||
_genericRoute(
|
||||
name: "customButtonScreen",
|
||||
child: const CustomButtonScreen(),
|
||||
),
|
||||
_genericRoute(
|
||||
name: "playerDecoderScreen",
|
||||
child: const PlayerDecoderScreen(),
|
||||
),
|
||||
_genericRoute(name: "playerAudioScreen", child: const PlayerAudioScreen()),
|
||||
_genericRoute(
|
||||
name: "playerAdvancedScreen",
|
||||
child: const PlayerAdvancedScreen(),
|
||||
),
|
||||
_genericRoute<ItemType?>(
|
||||
name: "calendarScreen",
|
||||
builder: (itemType) => CalendarScreen(itemType: itemType),
|
||||
),
|
||||
_genericRoute<Manga>(
|
||||
name: "migrate",
|
||||
builder: (manga) => MigrationScreen(manga: manga),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:mangayomi/eval/lib.dart';
|
||||
import 'package:mangayomi/eval/javascript/http.dart';
|
||||
|
|
@ -9,7 +10,6 @@ import 'package:mangayomi/models/chapter.dart';
|
|||
import 'package:mangayomi/models/page.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/manga/archive_reader/providers/archive_reader_providers.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/utils/utils.dart';
|
||||
import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ import 'package:path/path.dart' as p;
|
|||
part 'get_video_list.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<(List<Video>, bool, List<String>)> getVideoList(
|
||||
Future<(List<Video>, bool, List<String>, Directory?)> getVideoList(
|
||||
Ref ref, {
|
||||
required Chapter episode,
|
||||
}) async {
|
||||
final storageProvider = StorageProvider();
|
||||
final mpvDirectory = await storageProvider.getMpvDirectory();
|
||||
final mangaDirectory = await storageProvider.getMangaMainDirectory(episode);
|
||||
final isLocalArchive =
|
||||
episode.manga.value!.isLocalArchive! &&
|
||||
|
|
@ -52,6 +53,7 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
|
|||
[Video(path!, episode.name!, path, subtitles: subtitles)],
|
||||
true,
|
||||
infoHashes,
|
||||
mpvDirectory
|
||||
);
|
||||
}
|
||||
final source = getSource(
|
||||
|
|
@ -68,7 +70,7 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
|
|||
episode.url,
|
||||
episode.archivePath,
|
||||
);
|
||||
return (videos, false, [infohash ?? ""]);
|
||||
return (videos, false, [infohash ?? ""], mpvDirectory);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -91,7 +93,7 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
|
|||
}
|
||||
}
|
||||
}
|
||||
return (torrentList, false, infoHashes);
|
||||
return (torrentList, false, infoHashes, mpvDirectory);
|
||||
}
|
||||
|
||||
List<Video> list = await getExtensionService(
|
||||
|
|
@ -105,5 +107,5 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
|
|||
}
|
||||
}
|
||||
|
||||
return (videos, false, infoHashes);
|
||||
return (videos, false, infoHashes, mpvDirectory);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'get_video_list.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getVideoListHash() => r'aeed8a24962e960a374d6bc7294e798ad3d0c05e';
|
||||
String _$getVideoListHash() => r'140ac1ca572d6220b7791c4350a0b32e275535a4';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -35,7 +35,7 @@ const getVideoListProvider = GetVideoListFamily();
|
|||
|
||||
/// See also [getVideoList].
|
||||
class GetVideoListFamily
|
||||
extends Family<AsyncValue<(List<Video>, bool, List<String>)>> {
|
||||
extends Family<AsyncValue<(List<Video>, bool, List<String>, Directory?)>> {
|
||||
/// See also [getVideoList].
|
||||
const GetVideoListFamily();
|
||||
|
||||
|
|
@ -73,8 +73,8 @@ class GetVideoListFamily
|
|||
}
|
||||
|
||||
/// See also [getVideoList].
|
||||
class GetVideoListProvider
|
||||
extends AutoDisposeFutureProvider<(List<Video>, bool, List<String>)> {
|
||||
class GetVideoListProvider extends AutoDisposeFutureProvider<
|
||||
(List<Video>, bool, List<String>, Directory?)> {
|
||||
/// See also [getVideoList].
|
||||
GetVideoListProvider({
|
||||
required Chapter episode,
|
||||
|
|
@ -109,7 +109,7 @@ class GetVideoListProvider
|
|||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<(List<Video>, bool, List<String>)> Function(
|
||||
FutureOr<(List<Video>, bool, List<String>, Directory?)> Function(
|
||||
GetVideoListRef provider)
|
||||
create,
|
||||
) {
|
||||
|
|
@ -128,8 +128,8 @@ class GetVideoListProvider
|
|||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<(List<Video>, bool, List<String>)>
|
||||
createElement() {
|
||||
AutoDisposeFutureProviderElement<
|
||||
(List<Video>, bool, List<String>, Directory?)> createElement() {
|
||||
return _GetVideoListProviderElement(this);
|
||||
}
|
||||
|
||||
|
|
@ -149,15 +149,14 @@ class GetVideoListProvider
|
|||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin GetVideoListRef
|
||||
on AutoDisposeFutureProviderRef<(List<Video>, bool, List<String>)> {
|
||||
mixin GetVideoListRef on AutoDisposeFutureProviderRef<
|
||||
(List<Video>, bool, List<String>, Directory?)> {
|
||||
/// The parameter `episode` of this provider.
|
||||
Chapter get episode;
|
||||
}
|
||||
|
||||
class _GetVideoListProviderElement
|
||||
extends AutoDisposeFutureProviderElement<(List<Video>, bool, List<String>)>
|
||||
with GetVideoListRef {
|
||||
class _GetVideoListProviderElement extends AutoDisposeFutureProviderElement<
|
||||
(List<Video>, bool, List<String>, Directory?)> with GetVideoListRef {
|
||||
_GetVideoListProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ String dateFormat(
|
|||
bool useRelativeTimesTamps = true,
|
||||
String dateFormat = "",
|
||||
bool showHOURorMINUTE = false,
|
||||
bool showInDaysFuture = false,
|
||||
}) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final locale = currentLocale(context);
|
||||
|
|
@ -68,11 +69,17 @@ String dateFormat(
|
|||
date.isAfter(fiveDaysAgo) ||
|
||||
date.isAfter(sixDaysAgo) ||
|
||||
date.isAfter(aWeekAgo)) {
|
||||
final difference = today.difference(date).inDays;
|
||||
final difference = today.difference(date).inDays.abs();
|
||||
return switch (difference) {
|
||||
1 => l10n.n_day_ago(difference),
|
||||
!= 7 => l10n.n_days_ago(difference),
|
||||
_ => l10n.a_week_ago,
|
||||
1 =>
|
||||
showInDaysFuture
|
||||
? l10n.in_n_day(difference)
|
||||
: l10n.n_day_ago(difference),
|
||||
!= 7 =>
|
||||
showInDaysFuture
|
||||
? l10n.in_n_days(difference)
|
||||
: l10n.n_days_ago(difference),
|
||||
_ => showInDaysFuture ? l10n.next_week : l10n.a_week_ago,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,6 @@ class DiscordRPC {
|
|||
/// Start timestamp in millis
|
||||
final int startAt = DateTime.timestamp().millisecondsSinceEpoch;
|
||||
|
||||
/// Start timestamp in millis for the current chapter/episode
|
||||
int chapterStartAt = 0;
|
||||
|
||||
/// End timestamp in millis for the current chapter/episode
|
||||
int chapterEndAt = 0;
|
||||
|
||||
/// Temp var
|
||||
late bool rpcShowReadingWatchingProgress;
|
||||
|
||||
|
|
@ -91,6 +85,7 @@ class DiscordRPC {
|
|||
: "Reading";
|
||||
final title = chapter.manga.value!.name;
|
||||
final chapterTitle = chapter.name;
|
||||
final imageUrl = chapter.manga.value!.imageUrl;
|
||||
final rpcShowTitle = ref.read(rpcShowTitleStateProvider);
|
||||
final rpcShowCoverImage = ref.read(rpcShowCoverImageStateProvider);
|
||||
await updateActivity(
|
||||
|
|
@ -98,9 +93,10 @@ class DiscordRPC {
|
|||
state: rpcShowTitle && rpcShowReadingWatchingProgress
|
||||
? chapterTitle
|
||||
: "-----",
|
||||
assets: rpcShowCoverImage
|
||||
assets:
|
||||
rpcShowCoverImage && imageUrl != null && imageUrl.startsWith("http")
|
||||
? RPCAssets(
|
||||
largeImage: chapter.manga.value!.imageUrl,
|
||||
largeImage: imageUrl,
|
||||
largeText: rpcShowTitle ? chapter.manga.value!.name : "-----",
|
||||
smallImage: "app-icon",
|
||||
smallText: "Mangayomi",
|
||||
|
|
@ -130,32 +126,20 @@ class DiscordRPC {
|
|||
await updateActivity(timestamps: RPCTimestamps(start: startAt));
|
||||
}
|
||||
|
||||
Future<void> startChapterTimestamp(
|
||||
int offsetInMillis,
|
||||
int durationInMillis,
|
||||
Future<void> updateChapterTimestamp(
|
||||
Duration position,
|
||||
Duration duration,
|
||||
) async {
|
||||
if (!rpcShowReadingWatchingProgress) {
|
||||
return;
|
||||
}
|
||||
chapterStartAt = DateTime.timestamp().millisecondsSinceEpoch;
|
||||
chapterEndAt =
|
||||
DateTime.timestamp().millisecondsSinceEpoch + durationInMillis;
|
||||
await updateActivity(
|
||||
timestamps: RPCTimestamps(
|
||||
start: chapterStartAt,
|
||||
end: chapterEndAt - offsetInMillis,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateChapterTimestamp(int newOffsetInMillis) async {
|
||||
if (!rpcShowReadingWatchingProgress) {
|
||||
return;
|
||||
}
|
||||
await updateActivity(
|
||||
timestamps: RPCTimestamps(
|
||||
start: chapterStartAt,
|
||||
end: chapterEndAt - newOffsetInMillis,
|
||||
start: DateTime.timestamp().subtract(position).millisecondsSinceEpoch,
|
||||
end: DateTime.timestamp()
|
||||
.subtract(position)
|
||||
.add(duration)
|
||||
.millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'dart:ui';
|
|||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
|
||||
import 'package:mangayomi/utils/headers.dart';
|
||||
|
|
@ -20,6 +20,21 @@ extension LetExtension<T> on T {
|
|||
}
|
||||
}
|
||||
|
||||
extension MedianExtension on List<int> {
|
||||
int median() {
|
||||
var middle = length ~/ 2;
|
||||
if (length % 2 == 1) {
|
||||
return this[middle];
|
||||
} else {
|
||||
return ((this[middle - 1] + this[middle]) / 2).round();
|
||||
}
|
||||
}
|
||||
|
||||
int arithmeticMean() {
|
||||
return isNotEmpty ? (reduce((e1, e2) => e1 + e2) / length).round() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageProviderExtension on ImageProvider {
|
||||
Future<Uint8List?> getBytes(
|
||||
BuildContext context, {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
extension StringExtensions on String {
|
||||
String substringAfter(String pattern) {
|
||||
final startIndex = indexOf(pattern);
|
||||
|
|
@ -73,3 +76,22 @@ extension StringExtensions on String {
|
|||
].any((extension) => toLowerCase().endsWith(extension));
|
||||
}
|
||||
}
|
||||
|
||||
extension NativeStringExtensions on List<String> {
|
||||
Pointer<Pointer<Int8>> strListToPointer() {
|
||||
final strings = this;
|
||||
List<Pointer<Int8>> int8PointerList = strings
|
||||
.map((str) => str.toNativeUtf8().cast<Int8>())
|
||||
.toList();
|
||||
|
||||
final Pointer<Pointer<Int8>> pointerPointer = malloc.allocate(
|
||||
int8PointerList.length,
|
||||
);
|
||||
|
||||
strings.asMap().forEach((index, utf) {
|
||||
pointerPointer[index] = int8PointerList[index];
|
||||
});
|
||||
|
||||
return pointerPointer;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <media_kit_video/media_kit_video_plugin.h>
|
||||
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <volume_controller/volume_controller_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
#include <window_to_front/window_to_front_plugin.h>
|
||||
|
||||
|
|
@ -42,6 +43,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) volume_controller_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "VolumeControllerPlugin");
|
||||
volume_controller_plugin_register_with_registrar(volume_controller_registrar);
|
||||
g_autoptr(FlPluginRegistrar) window_manager_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
|
||||
window_manager_plugin_register_with_registrar(window_manager_registrar);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
media_kit_video
|
||||
screen_retriever_linux
|
||||
url_launcher_linux
|
||||
volume_controller
|
||||
window_manager
|
||||
window_to_front
|
||||
)
|
||||
|
|
|
|||
89
pubspec.lock
89
pubspec.lock
|
|
@ -1162,66 +1162,73 @@ packages:
|
|||
media_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit
|
||||
sha256: "48c10c3785df5d88f0eef970743f8c99b2e5da2b34b9d8f9876e598f62d9e776"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: media_kit
|
||||
ref: HEAD
|
||||
resolved-ref: baeecb6aa673e49a173448909a9e15d17d1a9c23
|
||||
url: "https://github.com/Schnitzel5/media-kit.git"
|
||||
source: git
|
||||
version: "1.2.0"
|
||||
media_kit_libs_android_video:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: media_kit_libs_android_video
|
||||
sha256: adff9b571b8ead0867f9f91070f8df39562078c0eb3371d88b9029a2d547d7b7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "libs/android/media_kit_libs_android_video"
|
||||
ref: HEAD
|
||||
resolved-ref: baeecb6aa673e49a173448909a9e15d17d1a9c23
|
||||
url: "https://github.com/Schnitzel5/media-kit.git"
|
||||
source: git
|
||||
version: "1.3.7"
|
||||
media_kit_libs_ios_video:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: media_kit_libs_ios_video
|
||||
sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "libs/ios/media_kit_libs_ios_video"
|
||||
ref: HEAD
|
||||
resolved-ref: baeecb6aa673e49a173448909a9e15d17d1a9c23
|
||||
url: "https://github.com/Schnitzel5/media-kit.git"
|
||||
source: git
|
||||
version: "1.1.4"
|
||||
media_kit_libs_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: media_kit_libs_linux
|
||||
sha256: "2b473399a49ec94452c4d4ae51cfc0f6585074398d74216092bf3d54aac37ecf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "libs/linux/media_kit_libs_linux"
|
||||
ref: HEAD
|
||||
resolved-ref: baeecb6aa673e49a173448909a9e15d17d1a9c23
|
||||
url: "https://github.com/Schnitzel5/media-kit.git"
|
||||
source: git
|
||||
version: "1.2.1"
|
||||
media_kit_libs_macos_video:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: media_kit_libs_macos_video
|
||||
sha256: f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "libs/macos/media_kit_libs_macos_video"
|
||||
ref: HEAD
|
||||
resolved-ref: baeecb6aa673e49a173448909a9e15d17d1a9c23
|
||||
url: "https://github.com/Schnitzel5/media-kit.git"
|
||||
source: git
|
||||
version: "1.1.4"
|
||||
media_kit_libs_video:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit_libs_video
|
||||
sha256: "958cc55e7065d9d01f52a2842dab2a0812a92add18489f1006d864fb5e42a3ef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "libs/universal/media_kit_libs_video"
|
||||
ref: HEAD
|
||||
resolved-ref: baeecb6aa673e49a173448909a9e15d17d1a9c23
|
||||
url: "https://github.com/Schnitzel5/media-kit.git"
|
||||
source: git
|
||||
version: "1.0.6"
|
||||
media_kit_libs_windows_video:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: media_kit_libs_windows_video
|
||||
sha256: dff76da2778729ab650229e6b4ec6ec111eb5151431002cbd7ea304ff1f112ab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "libs/windows/media_kit_libs_windows_video"
|
||||
ref: HEAD
|
||||
resolved-ref: baeecb6aa673e49a173448909a9e15d17d1a9c23
|
||||
url: "https://github.com/Schnitzel5/media-kit.git"
|
||||
source: git
|
||||
version: "1.0.11"
|
||||
media_kit_video:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: media_kit_video
|
||||
ref: aeb29faa8ea93a386ad1185b69fd6225fa331c74
|
||||
resolved-ref: aeb29faa8ea93a386ad1185b69fd6225fa331c74
|
||||
url: "https://github.com/media-kit/media-kit.git"
|
||||
ref: HEAD
|
||||
resolved-ref: baeecb6aa673e49a173448909a9e15d17d1a9c23
|
||||
url: "https://github.com/Schnitzel5/media-kit.git"
|
||||
source: git
|
||||
version: "1.3.0"
|
||||
meta:
|
||||
|
|
@ -1736,6 +1743,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
simple_gesture_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: simple_gesture_detector
|
||||
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
@ -1869,6 +1884,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
table_calendar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: table_calendar
|
||||
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -2081,10 +2104,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: volume_controller
|
||||
sha256: e82fd689bb8e1fe8e64be3fa5946ff8699058f8cf9f4c1679acdba20cda7f5bd
|
||||
sha256: d75039e69c0d90e7810bfd47e3eedf29ff8543ea7a10392792e81f9bded7edf5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.3"
|
||||
version: "3.4.0"
|
||||
wakelock_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
15
pubspec.yaml
15
pubspec.yaml
|
|
@ -41,13 +41,18 @@ dependencies:
|
|||
flutter_web_auth_2: ^3.1.2
|
||||
numberpicker: ^2.1.2
|
||||
encrypt: ^5.0.3
|
||||
media_kit: ^1.2.0
|
||||
media_kit:
|
||||
git:
|
||||
url: https://github.com/Schnitzel5/media-kit.git
|
||||
path: media_kit
|
||||
media_kit_video:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit.git
|
||||
url: https://github.com/Schnitzel5/media-kit.git
|
||||
path: media_kit_video
|
||||
ref: aeb29faa8ea93a386ad1185b69fd6225fa331c74
|
||||
media_kit_libs_video: ^1.0.6
|
||||
media_kit_libs_video:
|
||||
git:
|
||||
url: https://github.com/Schnitzel5/media-kit.git
|
||||
path: libs/universal/media_kit_libs_video
|
||||
crypto: ^3.0.6
|
||||
cupertino_icons: ^1.0.8
|
||||
window_manager: ^0.4.3
|
||||
|
|
@ -98,6 +103,7 @@ dependencies:
|
|||
git:
|
||||
url: https://github.com/Schnitzel5/flutter-discord-rpc.git
|
||||
ref: main
|
||||
table_calendar: ^3.2.0
|
||||
|
||||
dependency_overrides:
|
||||
ffi: ^2.1.3
|
||||
|
|
@ -156,3 +162,4 @@ inno_bundle:
|
|||
- french
|
||||
- german
|
||||
admin: false
|
||||
version: 0.6.3
|
||||
|
|
|
|||
Loading…
Reference in a new issue