mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-01-11 22:40:36 +00:00
Compare commits
88 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89dd50dc25 | ||
|
|
1f3fe65945 | ||
|
|
88f8d7b4be | ||
|
|
83b7d31e0a | ||
|
|
40fdfc2592 | ||
|
|
74b194602c | ||
|
|
b9f9a8398f | ||
|
|
9458ae120b | ||
|
|
701a696820 | ||
|
|
db24951673 | ||
|
|
7c4c8d8a20 | ||
|
|
6b70fff6cc | ||
|
|
2365e28a1a | ||
|
|
922582b270 | ||
|
|
79ee4b4fc8 | ||
|
|
c8328fa347 | ||
|
|
a63f0d67bd | ||
|
|
2c92d74a03 | ||
|
|
a2a10a799d | ||
|
|
ba77c5baea | ||
|
|
4e2d8b0038 | ||
|
|
821cbfa0dd | ||
|
|
4fb9f0a9df | ||
|
|
1ac605e30a | ||
|
|
9efd76581f | ||
|
|
7664e38cfd | ||
|
|
42f1dcff92 | ||
|
|
86fb19ecb2 | ||
|
|
bdcd28488e | ||
|
|
30e6d50210 | ||
|
|
66b508d65d | ||
|
|
004885d557 | ||
|
|
e463cce61a | ||
|
|
67a83c0e6a | ||
|
|
da566d3d0b | ||
|
|
c911594e73 | ||
|
|
9bd8a62d31 | ||
|
|
53cd2101f4 | ||
|
|
4e18e18489 | ||
|
|
481deb1344 | ||
|
|
344533aeb1 | ||
|
|
e40e1b8fe6 | ||
|
|
b5d37caaa9 | ||
|
|
e342fe16fb | ||
|
|
390e6fed46 | ||
|
|
19a051b660 | ||
|
|
5f7ea7fcf4 | ||
|
|
0f83899bac | ||
|
|
a078b59678 | ||
|
|
0ed8ee2cd2 | ||
|
|
67dee18776 | ||
|
|
76645d97c1 | ||
|
|
8fe910900b | ||
|
|
1e469614d9 | ||
|
|
284fccd1ef | ||
|
|
9ac6237caf | ||
|
|
0bfcdaddf4 | ||
|
|
fc49b33826 | ||
|
|
23e41373dc | ||
|
|
6e4d3dd52e | ||
|
|
2cade3db56 | ||
|
|
1b708d6884 | ||
|
|
77357312a0 | ||
|
|
85ff4d7d4c | ||
|
|
da7c32f71e | ||
|
|
e48c475fcb | ||
|
|
4e9af30e8e | ||
|
|
0789f4c85a | ||
|
|
3f065feeef | ||
|
|
23ff95afce | ||
|
|
04267b7a50 | ||
|
|
782d3963c1 | ||
|
|
29091e4b5f | ||
|
|
a306b10e5e | ||
|
|
3a1c69ef3f | ||
|
|
91b8c08658 | ||
|
|
a6df770275 | ||
|
|
9b5bae831e | ||
|
|
958e91ac9a | ||
|
|
6e5322137e | ||
|
|
30c74423ad | ||
|
|
c7e648a6d9 | ||
|
|
33152fc035 | ||
|
|
5fdbf530cb | ||
|
|
5c34dcab9a | ||
|
|
cc84c33c25 | ||
|
|
e0ecc94869 | ||
|
|
01b3ed9f24 |
190 changed files with 7151 additions and 4912 deletions
2
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
2
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
|
|
@ -74,7 +74,7 @@ body:
|
|||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/kodjodevf/mangayomi-extensions/issues/new/choose).
|
||||
- label: If this is an issue with an extension, I should be opening an issue in the extension's repository.
|
||||
required: true
|
||||
- label: I have updated all installed extensions.
|
||||
required: true
|
||||
|
|
|
|||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
|
@ -71,13 +71,6 @@ jobs:
|
|||
mv app-armeabi-v7a-release.apk Mangayomi-${{ github.ref_name }}-android-armeabi-v7a.apk
|
||||
mv app-x86_64-release.apk Mangayomi-${{ github.ref_name }}-android-x86_64.apk
|
||||
|
||||
- name: build android apk (all architectures)
|
||||
run: |
|
||||
export GRADLE_OPTS="-Xmx4096m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC"
|
||||
flutter build apk --release --verbose
|
||||
cd build/app/outputs/flutter-apk
|
||||
mv app-release.apk Mangayomi-${{ github.ref_name }}-android-universal.apk
|
||||
|
||||
- name: upload artifact android apks
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
|
@ -189,7 +182,7 @@ jobs:
|
|||
pod update
|
||||
cd ..
|
||||
flutter build macos --release --verbose
|
||||
brew install create-dmg
|
||||
brew install create-dmg
|
||||
create-dmg --volname Mangayomi-${{ github.ref_name }}-macos --window-pos 200 120 --window-size 800 450 --icon-size 100 --app-drop-link 600 185 Mangayomi-${{ github.ref_name }}-macos.dmg build/macos/Build/Products/Release/Mangayomi.app
|
||||
|
||||
- name: upload artifact macos dmg
|
||||
|
|
@ -314,6 +307,11 @@ jobs:
|
|||
# Copy built files
|
||||
cp -r build/linux/x64/release/bundle/* AppDir/usr/bin/
|
||||
cp -rL linux/packaging/icons/* AppDir/usr/share/icons
|
||||
# AppImage fix: Create Symlink AppDir/usr/bin/lib/libmpv.so to AppDir/usr/lib/libmpv.so.2
|
||||
if [ ! -e AppDir/usr/bin/lib/libmpv.so ]; then
|
||||
mkdir -p AppDir/usr/lib
|
||||
ln -s libmpv.so.2 AppDir/usr/lib/libmpv.so
|
||||
fi
|
||||
# Scan AppDir/usr/bin/lib for existing libraries to exclude them from linuxdeploy packaging
|
||||
EXCLUDE_LIBS=$(find AppDir/usr/bin/lib -type f -name "*.so*" -exec basename {} \; | sort -u)
|
||||
# Add --exclude-library flag to each found library
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
PODS:
|
||||
- app_links (6.4.1):
|
||||
- Flutter
|
||||
- audio_session (0.0.1):
|
||||
- app_links (7.0.0):
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
|
|
@ -57,9 +55,8 @@ PODS:
|
|||
- Flutter
|
||||
- isar_community_flutter_libs (1.0.0):
|
||||
- Flutter
|
||||
- just_audio (0.0.1):
|
||||
- m_extension_server (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- media_kit_libs_ios_video (1.0.4):
|
||||
- Flutter
|
||||
- media_kit_video (0.0.1):
|
||||
|
|
@ -81,26 +78,16 @@ PODS:
|
|||
- SDWebImage/Core (5.17.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SwiftyGif (5.4.4)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- volume_controller (0.0.1):
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- webview_flutter_wkwebview (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
|
|
@ -110,7 +97,7 @@ DEPENDENCIES:
|
|||
- flutter_qjs (from `.symlinks/plugins/flutter_qjs/ios`)
|
||||
- flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`)
|
||||
- isar_community_flutter_libs (from `.symlinks/plugins/isar_community_flutter_libs/ios`)
|
||||
- just_audio (from `.symlinks/plugins/just_audio/darwin`)
|
||||
- m_extension_server (from `.symlinks/plugins/m_extension_server/ios`)
|
||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
|
|
@ -119,12 +106,9 @@ DEPENDENCIES:
|
|||
- rust_lib_mangayomi (from `.symlinks/plugins/rust_lib_mangayomi/ios`)
|
||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
|
@ -137,8 +121,6 @@ SPEC REPOS:
|
|||
EXTERNAL SOURCES:
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
audio_session:
|
||||
:path: ".symlinks/plugins/audio_session/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
|
|
@ -157,8 +139,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/flutter_web_auth_2/ios"
|
||||
isar_community_flutter_libs:
|
||||
:path: ".symlinks/plugins/isar_community_flutter_libs/ios"
|
||||
just_audio:
|
||||
:path: ".symlinks/plugins/just_audio/darwin"
|
||||
m_extension_server:
|
||||
:path: ".symlinks/plugins/m_extension_server/ios"
|
||||
media_kit_libs_ios_video:
|
||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||
media_kit_video:
|
||||
|
|
@ -175,22 +157,15 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_player_avfoundation:
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||
volume_controller:
|
||||
:path: ".symlinks/plugins/volume_controller/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
webview_flutter_wkwebview:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
||||
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
|
||||
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
|
|
@ -202,24 +177,21 @@ SPEC CHECKSUMS:
|
|||
flutter_qjs: 1d5918f42171154e88dd545dd45f126c0291976f
|
||||
flutter_web_auth_2: 3464a7c16dc6480b6194fc89913bae6e82f28405
|
||||
isar_community_flutter_libs: bede843185a61a05ff364a05c9b23209523f7e0d
|
||||
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
|
||||
m_extension_server: 6946ec189542b271dbd15629b9498595f1036761
|
||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
rust_lib_mangayomi: 360a904274b47351a0f7c26d3ce5aa6392bb8db3
|
||||
screen_brightness_ios: 9953fd7da5bd480f1a93990daeec2eb42d4f3b52
|
||||
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
|
||||
|
||||
PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class DartExtensionService implements ExtensionService {
|
|||
|
||||
interpreter.execute(
|
||||
source: source.sourceCode!.replaceAll('Client(source)', 'Client()'),
|
||||
args: source.toMSource(),
|
||||
positionalArgs: [source.toMSource()],
|
||||
);
|
||||
return interpreter;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -367,6 +367,9 @@ void hasError(Response response) {
|
|||
final errorMessage = jsonDecode(response.body)['error'];
|
||||
final code = jsonDecode(response.body)['code'];
|
||||
if (errorMessage != null && code != null) {
|
||||
if ((code as int) == 403) {
|
||||
throw "errorMessage: Failed to bypass Cloudflare.\n\n\nYou can try to bypass it manually in the webview \n\n\nstatusCode: 403";
|
||||
}
|
||||
throw "errorMessage: $errorMessage \n\n\nstatusCode: $code";
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -75,14 +75,12 @@ class MBridge {
|
|||
var query = htmlXPath.query(xpath);
|
||||
if (query.nodes.length > 1) {
|
||||
for (var element in query.attrs) {
|
||||
attrs.add(element!.trim().trimLeft().trimRight());
|
||||
attrs.add(element!.trim());
|
||||
}
|
||||
}
|
||||
//Return one attr
|
||||
else if (query.nodes.length == 1) {
|
||||
String attr = query.attr != null
|
||||
? query.attr!.trim().trimLeft().trimRight()
|
||||
: "";
|
||||
String attr = query.attr != null ? query.attr!.trim() : "";
|
||||
if (attr.isNotEmpty) {
|
||||
attrs = [attr];
|
||||
}
|
||||
|
|
@ -102,7 +100,7 @@ class MBridge {
|
|||
statusMap = element;
|
||||
for (var element in statusMap.entries) {
|
||||
if (element.key.toString().toLowerCase().contains(
|
||||
status.toLowerCase().trim().trimLeft().trimRight(),
|
||||
status.toLowerCase().trim(),
|
||||
)) {
|
||||
return switch (element.value as int) {
|
||||
0 => Status.ongoing,
|
||||
|
|
|
|||
|
|
@ -451,6 +451,7 @@
|
|||
"downloaded_only": "المحملة فقط",
|
||||
"downloaded_only_description": "إظهار الإدخالات المحملة فقط في مكتبتك",
|
||||
"concurrent_downloads": "التحميلات المتزامنة",
|
||||
"logs_on": "تفعيل التسجيل",
|
||||
"share_app_logs": "مشاركة سجلات التطبيق",
|
||||
"no_app_logs": "لا يوجد ملف log.txt!",
|
||||
"failed": "فشل!",
|
||||
|
|
@ -560,4 +561,4 @@
|
|||
"show_scroll_percentage": "إظهار نسبة التمرير",
|
||||
"remove_extra_paragraph_spacing": "إزالة المسافات الإضافية بين الفقرات",
|
||||
"select_label_color": "تحديد لون {label}"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -305,6 +305,7 @@
|
|||
"default_subtitle_language": "ডিফল্ট উপশিৰোনাম ভাষা",
|
||||
"follow_system_theme": "ছিষ্টেম থিম অনুসৰণ কৰক",
|
||||
"concurrent_downloads": "সমসাময়িক ডাউনলোড",
|
||||
"logs_on": "লগিং সক্ষম কৰক",
|
||||
"share_app_logs": "এপ লগ শ্বেয়াৰ কৰক",
|
||||
"no_app_logs": "কোনো log.txt ফাইল উপলব্ধ নাই!",
|
||||
"failed": "বিফল!",
|
||||
|
|
|
|||
|
|
@ -465,6 +465,7 @@
|
|||
"downloaded_only": "Nur heruntergeladene",
|
||||
"downloaded_only_description": "Nur heruntergeladene Einträge in deiner Bibliothek anzeigen",
|
||||
"concurrent_downloads": "Gleichzeitige Downloads",
|
||||
"logs_on": "Protokollierung aktivieren",
|
||||
"share_app_logs": "App-Protokolle teilen",
|
||||
"no_app_logs": "Keine log.txt Datei verfügbar!",
|
||||
"failed": "Fehlgeschlagen!",
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@
|
|||
"nsfw_sources_info": "This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app",
|
||||
"version": "Version",
|
||||
"check_for_update": "Check for update",
|
||||
"logs_on": "Enable logging",
|
||||
"share_app_logs": "Share app logs",
|
||||
"no_app_logs": "No log.txt available!",
|
||||
"failed": "Failed!",
|
||||
|
|
@ -559,5 +560,8 @@
|
|||
"line_height": "Line Height",
|
||||
"show_scroll_percentage": "Show Scroll Percentage",
|
||||
"remove_extra_paragraph_spacing": "Remove Extra Paragraph Spacing",
|
||||
"select_label_color": "Select {label} Color"
|
||||
"select_label_color": "Select {label} Color",
|
||||
"default_user_agent": "Defaul user agent",
|
||||
"forceLandscapeMode": "Force landscape mode",
|
||||
"forceLandscapeModeSubtitle": "Force the player to use landscape orientation."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@
|
|||
"downloaded_only": "Solo descargados",
|
||||
"downloaded_only_description": "Mostrar solo entradas descargadas en tu biblioteca",
|
||||
"concurrent_downloads": "Descargas simultáneas",
|
||||
"logs_on": "Activar registro",
|
||||
"share_app_logs": "Compartir registros de la aplicación",
|
||||
"no_app_logs": "¡No hay archivo log.txt disponible!",
|
||||
"failed": "¡Fallido!",
|
||||
|
|
|
|||
|
|
@ -451,6 +451,7 @@
|
|||
"downloaded_only": "Solo descargados",
|
||||
"downloaded_only_description": "Mostrar solo entradas descargadas en tu biblioteca",
|
||||
"concurrent_downloads": "Descargas simultáneas",
|
||||
"logs_on": "Habilitar registro",
|
||||
"share_app_logs": "Compartir registros de la aplicación",
|
||||
"no_app_logs": "¡No hay archivo log.txt disponible!",
|
||||
"failed": "¡Fallido!",
|
||||
|
|
|
|||
|
|
@ -455,6 +455,7 @@
|
|||
"downloaded_only": "Téléchargés uniquement",
|
||||
"downloaded_only_description": "Afficher uniquement les entrées téléchargées dans votre bibliothèque",
|
||||
"concurrent_downloads": "Téléchargements simultanés",
|
||||
"logs_on": "Activer la journalisation",
|
||||
"share_app_logs": "Partager les journaux de l'application",
|
||||
"no_app_logs": "Aucun fichier log.txt disponible !",
|
||||
"failed": "Échoué !",
|
||||
|
|
|
|||
|
|
@ -305,6 +305,7 @@
|
|||
"default_subtitle_language": "डिफ़ॉल्ट उपशीर्षक भाषा",
|
||||
"follow_system_theme": "सिस्टम थीम का पालन करें",
|
||||
"concurrent_downloads": "समवर्ती डाउनलोड",
|
||||
"logs_on": "लॉगिंग सक्षम करें",
|
||||
"share_app_logs": "ऐप लॉग साझा करें",
|
||||
"no_app_logs": "कोई log.txt फ़ाइल उपलब्ध नहीं!",
|
||||
"failed": "विफल!",
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@
|
|||
"downloaded_only": "Hanya yang diunduh",
|
||||
"downloaded_only_description": "Hanya tampilkan entri yang diunduh di perpustakaan Anda",
|
||||
"concurrent_downloads": "Unduhan bersamaan",
|
||||
"logs_on": "Aktifkan pencatatan",
|
||||
"share_app_logs": "Bagikan log aplikasi",
|
||||
"no_app_logs": "Tidak ada file log.txt tersedia!",
|
||||
"failed": "Gagal!",
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@
|
|||
"downloaded_only": "Solo scaricati",
|
||||
"downloaded_only_description": "Mostra solo le voci scaricate nella tua libreria",
|
||||
"concurrent_downloads": "Download simultanei",
|
||||
"logs_on": "Abilita registrazione",
|
||||
"share_app_logs": "Condividi i log dell'app",
|
||||
"no_app_logs": "Nessun file log.txt disponibile!",
|
||||
"failed": "Fallito!",
|
||||
|
|
|
|||
|
|
@ -304,6 +304,7 @@
|
|||
"default_subtitle_language": "デフォルト字幕言語",
|
||||
"follow_system_theme": "システムテーマに従う",
|
||||
"concurrent_downloads": "同時ダウンロード",
|
||||
"logs_on": "ログを有効にする",
|
||||
"share_app_logs": "アプリログを共有",
|
||||
"no_app_logs": "log.txtファイルが利用できません!",
|
||||
"failed": "失敗!",
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@
|
|||
"downloaded_only": "Apenas baixados",
|
||||
"downloaded_only_description": "Mostrar apenas entradas baixadas na sua biblioteca",
|
||||
"concurrent_downloads": "Downloads simultâneos",
|
||||
"logs_on": "Ativar registro",
|
||||
"share_app_logs": "Compartilhar logs do aplicativo",
|
||||
"no_app_logs": "Nenhum arquivo log.txt disponível!",
|
||||
"failed": "Falhou!",
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@
|
|||
"downloaded_only": "Apenas baixados",
|
||||
"downloaded_only_description": "Mostrar apenas entradas baixadas na sua biblioteca",
|
||||
"concurrent_downloads": "Downloads simultâneos",
|
||||
"logs_on": "Ativar registro",
|
||||
"share_app_logs": "Compartilhar logs do aplicativo",
|
||||
"no_app_logs": "Nenhum arquivo log.txt disponível!",
|
||||
"failed": "Falhou!",
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@
|
|||
"downloaded_only": "Только загруженные",
|
||||
"downloaded_only_description": "Показывать только загруженные записи в вашей библиотеке",
|
||||
"concurrent_downloads": "Одновременные загрузки",
|
||||
"logs_on": "Включить ведение журнала",
|
||||
"share_app_logs": "Поделиться журналами приложения",
|
||||
"no_app_logs": "Файл log.txt недоступен!",
|
||||
"failed": "Не удалось!",
|
||||
|
|
|
|||
|
|
@ -452,6 +452,7 @@
|
|||
"downloaded_only": "ที่ดาวน์โหลดแล้วเท่านั้น",
|
||||
"downloaded_only_description": "แสดงเฉพาะรายการที่ดาวน์โหลดแล้วในห้องสมุดของคุณ",
|
||||
"concurrent_downloads": "ดาวน์โหลดพร้อมกัน",
|
||||
"logs_on": "เปิดการบันทึก",
|
||||
"share_app_logs": "แชร์บันทึกแอป",
|
||||
"no_app_logs": "ไม่มีไฟล์ log.txt!",
|
||||
"failed": "ล้มเหลว!",
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@
|
|||
"downloaded_only": "Sadece indirilmiş",
|
||||
"downloaded_only_description": "Kütüphanenizde yalnızca indirilmiş girişleri göster",
|
||||
"concurrent_downloads": "Eş zamanlı indirmeler",
|
||||
"logs_on": "Günlük kaydını etkinleştir",
|
||||
"share_app_logs": "Uygulama günlüklerini paylaş",
|
||||
"no_app_logs": "log.txt dosyası yok!",
|
||||
"failed": "Başarısız!",
|
||||
|
|
|
|||
|
|
@ -454,6 +454,7 @@
|
|||
"downloaded_only": "仅已下载",
|
||||
"downloaded_only_description": "仅显示库中已下载的条目",
|
||||
"concurrent_downloads": "并发下载",
|
||||
"logs_on": "启用日志",
|
||||
"share_app_logs": "分享应用日志",
|
||||
"no_app_logs": "没有可用的 log.txt 文件!",
|
||||
"failed": "失败!",
|
||||
|
|
@ -562,5 +563,8 @@
|
|||
"line_height": "行高",
|
||||
"show_scroll_percentage": "显示滚动百分比",
|
||||
"remove_extra_paragraph_spacing": "删除额外的段落间距",
|
||||
"select_label_color": "选择 {label} 颜色"
|
||||
}
|
||||
"select_label_color": "选择 {label} 颜色",
|
||||
"default_user_agent": "默认用户代理",
|
||||
"forceLandscapeMode": "强制横屏模式",
|
||||
"forceLandscapeModeSubtitle": "强制播放器使用横屏方向。"
|
||||
}
|
||||
|
|
@ -935,6 +935,12 @@ abstract class AppLocalizations {
|
|||
/// **'Check for update'**
|
||||
String get check_for_update;
|
||||
|
||||
/// No description provided for @logs_on.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable logging'**
|
||||
String get logs_on;
|
||||
|
||||
/// No description provided for @share_app_logs.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3430,6 +3436,24 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Select {label} Color'**
|
||||
String select_label_color(Object label);
|
||||
|
||||
/// No description provided for @default_user_agent.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Defaul user agent'**
|
||||
String get default_user_agent;
|
||||
|
||||
/// No description provided for @forceLandscapeMode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Force landscape mode'**
|
||||
String get forceLandscapeMode;
|
||||
|
||||
/// No description provided for @forceLandscapeModeSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Force the player to use landscape orientation.'**
|
||||
String get forceLandscapeModeSubtitle;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -434,6 +434,9 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'التحقق من التحديثات';
|
||||
|
||||
@override
|
||||
String get logs_on => 'تفعيل التسجيل';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'مشاركة سجلات التطبيق';
|
||||
|
||||
|
|
@ -1775,4 +1778,14 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'تحديد لون $label';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -436,6 +436,9 @@ class AppLocalizationsAs extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'আপডেটৰ বাবে পৰীক্ষা কৰক';
|
||||
|
||||
@override
|
||||
String get logs_on => 'লগিং সক্ষম কৰক';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'এপ লগ শ্বেয়াৰ কৰক';
|
||||
|
||||
|
|
@ -1781,4 +1784,14 @@ class AppLocalizationsAs extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return '$label ৰং নিৰ্বাচন কৰক';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -438,6 +438,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Auf Aktualisierung prüfen';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Protokollierung aktivieren';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'App-Protokolle teilen';
|
||||
|
||||
|
|
@ -1796,4 +1799,14 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'Farbe für $label auswählen';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -436,6 +436,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Check for update';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Enable logging';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Share app logs';
|
||||
|
||||
|
|
@ -1775,4 +1778,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'Select $label Color';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -440,6 +440,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Buscar actualizaciones';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Activar registro';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Compartir registros de la aplicación';
|
||||
|
||||
|
|
@ -1804,6 +1807,16 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'Seleccionar color de $label';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
||||
/// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`).
|
||||
|
|
@ -2242,6 +2255,9 @@ class AppLocalizationsEs419 extends AppLocalizationsEs {
|
|||
@override
|
||||
String get check_for_update => 'Buscar actualizaciones';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Habilitar registro';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Compartir registros de la aplicación';
|
||||
|
||||
|
|
|
|||
|
|
@ -442,6 +442,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Rechercher des mises à jour';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Activer la journalisation';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Partager les journaux de l\'application';
|
||||
|
||||
|
|
@ -1804,4 +1807,14 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'Sélectionner la couleur $label';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -436,6 +436,9 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'अपडेट के लिए जांचें';
|
||||
|
||||
@override
|
||||
String get logs_on => 'लॉगिंग सक्षम करें';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'ऐप लॉग साझा करें';
|
||||
|
||||
|
|
@ -1781,4 +1784,14 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return '$label रंग चुनें';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -440,6 +440,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Periksa Pembaruan';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Aktifkan pencatatan';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Bagikan log aplikasi';
|
||||
|
||||
|
|
@ -1787,4 +1790,14 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'Pilih Warna $label';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -440,6 +440,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Controlla aggiornamenti';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Abilita registrazione';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Condividi i log dell\'app';
|
||||
|
||||
|
|
@ -1801,4 +1804,14 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'Seleziona colore $label';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -431,6 +431,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Check for update';
|
||||
|
||||
@override
|
||||
String get logs_on => 'ログを有効にする';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'アプリログを共有';
|
||||
|
||||
|
|
@ -1752,4 +1755,14 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return '$labelの色を選択';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -440,6 +440,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Verificar atualização';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Ativar registro';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Compartilhar logs do aplicativo';
|
||||
|
||||
|
|
@ -1799,6 +1802,16 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'Selecionar cor de $label';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
||||
/// The translations for Portuguese, as used in Brazil (`pt_BR`).
|
||||
|
|
@ -2237,6 +2250,9 @@ class AppLocalizationsPtBr extends AppLocalizationsPt {
|
|||
@override
|
||||
String get check_for_update => 'Verificar atualização';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Ativar registro';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Compartilhar logs do aplicativo';
|
||||
|
||||
|
|
|
|||
|
|
@ -441,6 +441,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Проверить обновления';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Включить ведение журнала';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Поделиться журналами приложения';
|
||||
|
||||
|
|
@ -1804,4 +1807,14 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'Выбрать цвет $label';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -436,6 +436,9 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'ตรวจสอบการอัพเดท';
|
||||
|
||||
@override
|
||||
String get logs_on => 'เปิดการบันทึก';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'แชร์บันทึกแอป';
|
||||
|
||||
|
|
@ -1775,4 +1778,14 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return 'เลือกสี $label';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -436,6 +436,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => 'Güncelleme Kontrol Et';
|
||||
|
||||
@override
|
||||
String get logs_on => 'Günlük kaydını etkinleştir';
|
||||
|
||||
@override
|
||||
String get share_app_logs => 'Uygulama günlüklerini paylaş';
|
||||
|
||||
|
|
@ -1787,4 +1790,14 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return '$label Rengini Seç';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => 'Defaul user agent';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => 'Force landscape mode';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle =>
|
||||
'Force the player to use landscape orientation.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -427,6 +427,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get check_for_update => '检查更新';
|
||||
|
||||
@override
|
||||
String get logs_on => '启用日志';
|
||||
|
||||
@override
|
||||
String get share_app_logs => '分享应用日志';
|
||||
|
||||
|
|
@ -1733,4 +1736,13 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
String select_label_color(Object label) {
|
||||
return '选择 $label 颜色';
|
||||
}
|
||||
|
||||
@override
|
||||
String get default_user_agent => '默认用户代理';
|
||||
|
||||
@override
|
||||
String get forceLandscapeMode => '强制横屏模式';
|
||||
|
||||
@override
|
||||
String get forceLandscapeModeSubtitle => '强制播放器使用横屏方向。';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_
|
|||
import 'package:mangayomi/l10n/generated/app_localizations.dart';
|
||||
import 'package:mangayomi/services/http/m_client.dart';
|
||||
import 'package:mangayomi/services/isolate_service.dart';
|
||||
import 'package:mangayomi/services/m_extension_server.dart';
|
||||
import 'package:mangayomi/services/download_manager/m_downloader.dart';
|
||||
import 'package:mangayomi/src/rust/frb_generated.dart';
|
||||
import 'package:mangayomi/utils/discord_rpc.dart';
|
||||
import 'package:mangayomi/utils/log/logger.dart';
|
||||
|
|
@ -50,7 +52,6 @@ DiscordRPC? discordRpc;
|
|||
WebViewEnvironment? webViewEnvironment;
|
||||
String? customDns;
|
||||
void main(List<String> args) async {
|
||||
cfResolutionWebviewServer();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
if (Platform.isLinux && runWebViewTitleBarWidget(args)) return;
|
||||
MediaKit.ensureInitialized();
|
||||
|
|
@ -83,6 +84,7 @@ void main(List<String> args) async {
|
|||
|
||||
Future<void> _postLaunchInit(StorageProvider storage) async {
|
||||
await AppLogger.init();
|
||||
unawaited(MDownloader.initializeIsolatePool(poolSize: 6));
|
||||
final hivePath = (Platform.isIOS || Platform.isMacOS)
|
||||
? "databases"
|
||||
: p.join("Mangayomi", "databases");
|
||||
|
|
@ -93,6 +95,7 @@ Future<void> _postLaunchInit(StorageProvider storage) async {
|
|||
await discordRpc?.initialize();
|
||||
}
|
||||
await storage.deleteBtDirectory();
|
||||
await cfResolutionWebviewServer();
|
||||
}
|
||||
|
||||
class MyApp extends ConsumerStatefulWidget {
|
||||
|
|
@ -118,7 +121,10 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
unawaited(ref.read(scanLocalLibraryProvider.future));
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
MExtensionServerPlatform(ref).startServer();
|
||||
if (ref.read(clearChapterCacheOnAppLaunchStateProvider)) {
|
||||
// Watch before calling clearcache to keep it alive, so that _getTotalDiskSpace completes safely
|
||||
ref.watch(totalChapterCacheSizeStateProvider);
|
||||
ref
|
||||
.read(totalChapterCacheSizeStateProvider.notifier)
|
||||
.clearCache(showToast: false);
|
||||
|
|
@ -155,8 +161,10 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
MExtensionServerPlatform(ref).stopServer();
|
||||
_linkSubscription?.cancel();
|
||||
discordRpc?.destroy();
|
||||
stopCfResolutionWebviewServer();
|
||||
AppLogger.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ class Settings {
|
|||
|
||||
int? pagePreloadAmount;
|
||||
|
||||
bool? enableLogs;
|
||||
|
||||
bool? checkForAppUpdates;
|
||||
|
||||
bool? checkForExtensionUpdates;
|
||||
|
|
@ -161,6 +163,8 @@ class Settings {
|
|||
|
||||
bool? fullScreenPlayer;
|
||||
|
||||
bool? forceLandscapePlayer;
|
||||
|
||||
bool? updateProgressAfterReading;
|
||||
|
||||
bool? enableAniSkip;
|
||||
|
|
@ -357,6 +361,7 @@ class Settings {
|
|||
this.sortLibraryAnime,
|
||||
this.pagePreloadAmount = 6,
|
||||
this.scaleType = ScaleType.fitScreen,
|
||||
this.enableLogs = false,
|
||||
this.checkForAppUpdates = true,
|
||||
this.checkForExtensionUpdates = true,
|
||||
this.backgroundColor = BackgroundColor.black,
|
||||
|
|
@ -372,6 +377,7 @@ class Settings {
|
|||
this.defaultDoubleTapToSkipLength = 10,
|
||||
this.defaultPlayBackSpeed = 1.0,
|
||||
this.fullScreenPlayer = false,
|
||||
this.forceLandscapePlayer = false,
|
||||
this.updateProgressAfterReading = true,
|
||||
this.enableAniSkip,
|
||||
this.enableAutoSkip,
|
||||
|
|
@ -482,6 +488,7 @@ class Settings {
|
|||
.map((e) => ChapterPageurls.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
enableLogs = json['enableLogs'];
|
||||
checkForAppUpdates = json['checkForAppUpdates'];
|
||||
checkForExtensionUpdates = json['checkForExtensionUpdates'];
|
||||
if (json['cookiesList'] != null) {
|
||||
|
|
@ -577,6 +584,7 @@ class Settings {
|
|||
? json['defaultPlayBackSpeed']
|
||||
: (json['defaultPlayBackSpeed'] as int).toDouble();
|
||||
fullScreenPlayer = json['fullScreenPlayer'];
|
||||
forceLandscapePlayer = json['forceLandscapePlayer'];
|
||||
updateProgressAfterReading = json['updateProgressAfterReading'];
|
||||
enableAniSkip = json['enableAniSkip'];
|
||||
enableAutoSkip = json['enableAutoSkip'];
|
||||
|
|
@ -720,6 +728,7 @@ class Settings {
|
|||
?.map((v) => v.toJson())
|
||||
.toList(),
|
||||
'chapterPageUrlsList': chapterPageUrlsList?.map((v) => v.toJson()).toList(),
|
||||
'enableLogs': enableLogs,
|
||||
'checkForAppUpdates': checkForAppUpdates,
|
||||
'checkForExtensionUpdates': checkForExtensionUpdates,
|
||||
'cookiesList': cookiesList,
|
||||
|
|
@ -782,6 +791,7 @@ class Settings {
|
|||
'defaultDoubleTapToSkipLength': defaultDoubleTapToSkipLength,
|
||||
'defaultPlayBackSpeed': defaultPlayBackSpeed,
|
||||
'fullScreenPlayer': fullScreenPlayer,
|
||||
'forceLandscapePlayer': forceLandscapePlayer,
|
||||
'updateProgressAfterReading': updateProgressAfterReading,
|
||||
'enableAniSkip': enableAniSkip,
|
||||
'enableAutoSkip': enableAutoSkip,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -12,6 +12,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_qjs/quickjs/ffi.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' as riv;
|
||||
import 'package:isar_community/isar.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
|
|
@ -29,7 +30,6 @@ import 'package:mangayomi/modules/anime/widgets/mobile.dart';
|
|||
import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
|
||||
import 'package:mangayomi/modules/anime/widgets/subtitle_setting_widget.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/custom_buttons_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_audio_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_decoder_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||
|
|
@ -48,11 +48,11 @@ import 'package:media_kit/generated/libmpv/bindings.dart' as generated;
|
|||
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:numberpicker/numberpicker.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
import 'package:window_manager/window_manager.dart' show windowManager;
|
||||
|
||||
import 'widgets/search_subtitles.dart';
|
||||
|
||||
|
|
@ -193,7 +193,10 @@ enum _AniSkipPhase { none, opening, ending }
|
|||
bool _firstTime = true;
|
||||
|
||||
class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||
with TickerProviderStateMixin, WidgetsBindingObserver {
|
||||
with
|
||||
_AlwaysOnTopStateMixin,
|
||||
TickerProviderStateMixin,
|
||||
WidgetsBindingObserver {
|
||||
late final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
|
||||
late final useLibass = ref.read(useLibassStateProvider);
|
||||
late final useMpvConfig = ref.read(useMpvConfigStateProvider);
|
||||
|
|
@ -667,7 +670,11 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
|
||||
Future<void> _initCustomButton() async {
|
||||
if (!useMpvConfig) return;
|
||||
final customButtons = await ref.read(getCustomButtonsStreamProvider.future);
|
||||
final customButtons = isar.customButtons
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.sortByPos()
|
||||
.findAllSync();
|
||||
if (customButtons.isEmpty) return;
|
||||
final primaryButton =
|
||||
customButtons.firstWhereOrNull((e) => e.isFavourite ?? false) ??
|
||||
|
|
@ -875,6 +882,12 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
});
|
||||
_firstTime = false;
|
||||
}
|
||||
if (!_isDesktop) {
|
||||
final forceLandscape = ref.read(forceLandscapePlayerStateProvider);
|
||||
if (forceLandscape) {
|
||||
_setLandscapeMode(true);
|
||||
}
|
||||
}
|
||||
_currentPositionSub = _player.stream.position.listen(
|
||||
_unifiedPositionHandler,
|
||||
);
|
||||
|
|
@ -922,7 +935,7 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
if (Platform.isAndroid && useLibass) {
|
||||
try {
|
||||
final subDir = await getApplicationDocumentsDirectory();
|
||||
final fontPath = p.join(subDir.path, 'subfont.ttf');
|
||||
final fontPath = path.join(subDir.path, 'subfont.ttf');
|
||||
final data = await rootBundle.load('assets/fonts/subfont.ttf');
|
||||
final bytes = data.buffer.asInt8List(
|
||||
data.offsetInBytes,
|
||||
|
|
@ -964,30 +977,31 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
@override
|
||||
void dispose() {
|
||||
_currentPosition.removeListener(_updateRpcTimestamp);
|
||||
_subDelayController.removeListener(_onSubDelayChanged);
|
||||
_subSpeedController.removeListener(_onSubSpeedChanged);
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_setCurrentPosition(true);
|
||||
_player.dispose();
|
||||
_player.stop();
|
||||
_completed.cancel();
|
||||
_currentPositionSub.cancel();
|
||||
_currentTotalDurationSub.cancel();
|
||||
_completed.cancel();
|
||||
_currentPosition.dispose();
|
||||
_currentTotalDuration.dispose();
|
||||
_video.dispose();
|
||||
_playbackSpeed.dispose();
|
||||
_isDoubleSpeed.dispose();
|
||||
_currentTotalDuration.dispose();
|
||||
_showFitLabel.dispose();
|
||||
_isCompleted.dispose();
|
||||
_tempPosition.dispose();
|
||||
_fit.dispose();
|
||||
if (!_isDesktop) {
|
||||
_setLandscapeMode(false);
|
||||
}
|
||||
_skipPhase.dispose();
|
||||
discordRpc?.showIdleText();
|
||||
discordRpc?.showOriginalTimestamp();
|
||||
_currentPosition.dispose();
|
||||
_subDelayController.dispose();
|
||||
_subSpeedController.dispose();
|
||||
if (!_isDesktop) _setLandscapeMode(false);
|
||||
discordRpc?.showIdleText();
|
||||
discordRpc?.showOriginalTimestamp();
|
||||
_streamController.keepAliveLink?.close();
|
||||
_player.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -1958,6 +1972,17 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
),
|
||||
Row(
|
||||
children: [
|
||||
if (_supportAlwaysOnTop())
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_alwaysOnTop ? Icons.push_pin : Icons.push_pin_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() => _alwaysOnTop = !_alwaysOnTop);
|
||||
windowManager.setAlwaysOnTop(_alwaysOnTop);
|
||||
},
|
||||
),
|
||||
btnToShowChapterListDialog(
|
||||
context,
|
||||
context.l10n.episodes,
|
||||
|
|
@ -2290,7 +2315,9 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
);
|
||||
final dir = await StorageProvider()
|
||||
.getGalleryDirectory();
|
||||
final file = File(p.join(dir!.path, "$name.png"));
|
||||
final file = File(
|
||||
path.join(dir!.path, "$name.png"),
|
||||
);
|
||||
file.writeAsBytesSync(imageBytes!);
|
||||
if (context.mounted) {
|
||||
botToast(context.l10n.picture_saved, second: 3);
|
||||
|
|
@ -2369,3 +2396,44 @@ class VideoPrefs {
|
|||
this.title,
|
||||
});
|
||||
}
|
||||
|
||||
mixin _AlwaysOnTopStateMixin<T extends StatefulWidget> on State<T> {
|
||||
// The original alwaysOnTop state.
|
||||
// This will be used to restore the original state when the widget disposed.
|
||||
bool? _savedAlwaysOnTop;
|
||||
|
||||
bool _alwaysOnTop = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initAlwaysOnTop();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_disposeAlwaysOnTop();
|
||||
}
|
||||
|
||||
Future<void> _initAlwaysOnTop() async {
|
||||
if (_supportAlwaysOnTop()) {
|
||||
_savedAlwaysOnTop = await windowManager.isAlwaysOnTop();
|
||||
if (mounted) {
|
||||
setState(() => _alwaysOnTop = _savedAlwaysOnTop!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _disposeAlwaysOnTop() async {
|
||||
if (_supportAlwaysOnTop()) {
|
||||
if (_savedAlwaysOnTop != null) {
|
||||
await windowManager.setAlwaysOnTop(_savedAlwaysOnTop!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the platform support AlwaysOnTop feature.
|
||||
bool _supportAlwaysOnTop() =>
|
||||
!kIsWeb && (Platform.isLinux || Platform.isMacOS || Platform.isWindows);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ part of 'anime_player_controller_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(AnimeStreamController)
|
||||
const animeStreamControllerProvider = AnimeStreamControllerFamily._();
|
||||
final animeStreamControllerProvider = AnimeStreamControllerFamily._();
|
||||
|
||||
final class AnimeStreamControllerProvider
|
||||
extends $NotifierProvider<AnimeStreamController, KeepAliveLink> {
|
||||
const AnimeStreamControllerProvider._({
|
||||
AnimeStreamControllerProvider._({
|
||||
required AnimeStreamControllerFamily super.from,
|
||||
required Chapter super.argument,
|
||||
}) : super(
|
||||
|
|
@ -70,7 +70,7 @@ final class AnimeStreamControllerFamily extends $Family
|
|||
KeepAliveLink,
|
||||
Chapter
|
||||
> {
|
||||
const AnimeStreamControllerFamily._()
|
||||
AnimeStreamControllerFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'animeStreamControllerProvider',
|
||||
|
|
@ -94,7 +94,6 @@ abstract class _$AnimeStreamController extends $Notifier<KeepAliveLink> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(episode: _$args);
|
||||
final ref = this.ref as $Ref<KeepAliveLink, KeepAliveLink>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -104,6 +103,6 @@ abstract class _$AnimeStreamController extends $Notifier<KeepAliveLink> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(episode: _$args));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ part of 'state_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(SubtitleSettingsState)
|
||||
const subtitleSettingsStateProvider = SubtitleSettingsStateProvider._();
|
||||
final subtitleSettingsStateProvider = SubtitleSettingsStateProvider._();
|
||||
|
||||
final class SubtitleSettingsStateProvider
|
||||
extends $NotifierProvider<SubtitleSettingsState, PlayerSubtitleSettings> {
|
||||
const SubtitleSettingsStateProvider._()
|
||||
SubtitleSettingsStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -50,7 +50,6 @@ abstract class _$SubtitleSettingsState
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref =
|
||||
this.ref as $Ref<PlayerSubtitleSettings, PlayerSubtitleSettings>;
|
||||
final element =
|
||||
|
|
@ -61,6 +60,6 @@ abstract class _$SubtitleSettingsState
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:mangayomi/modules/browse/extension/extension_screen.dart';
|
|||
import 'package:mangayomi/modules/browse/sources/sources_screen.dart';
|
||||
import 'package:mangayomi/modules/library/widgets/search_text_form_field.dart';
|
||||
import 'package:mangayomi/services/fetch_sources_list.dart';
|
||||
import 'package:mangayomi/utils/item_type_localization.dart';
|
||||
|
||||
class BrowseScreen extends ConsumerStatefulWidget {
|
||||
const BrowseScreen({super.key});
|
||||
|
|
@ -20,19 +21,35 @@ class BrowseScreen extends ConsumerStatefulWidget {
|
|||
ConsumerState<BrowseScreen> createState() => _BrowseScreenState();
|
||||
}
|
||||
|
||||
enum BrowseTabKind { sources, extensions }
|
||||
|
||||
class BrowseTab {
|
||||
final ItemType type;
|
||||
final BrowseTabKind kind;
|
||||
|
||||
const BrowseTab(this.type, this.kind);
|
||||
}
|
||||
|
||||
class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
||||
with TickerProviderStateMixin {
|
||||
late final hideItems = ref.read(hideItemsStateProvider);
|
||||
final _textEditingController = TextEditingController();
|
||||
late TabController _tabBarController;
|
||||
|
||||
late final _tabList = [
|
||||
if (!hideItems.contains("/MangaLibrary")) 'manga',
|
||||
if (!hideItems.contains("/AnimeLibrary")) 'anime',
|
||||
if (!hideItems.contains("/NovelLibrary")) 'novel',
|
||||
if (!hideItems.contains("/MangaLibrary")) 'mangaExtension',
|
||||
if (!hideItems.contains("/AnimeLibrary")) 'animeExtension',
|
||||
if (!hideItems.contains("/NovelLibrary")) 'novelExtension',
|
||||
late final List<BrowseTab> _tabList = [
|
||||
if (!hideItems.contains("/MangaLibrary"))
|
||||
BrowseTab(ItemType.manga, BrowseTabKind.sources),
|
||||
if (!hideItems.contains("/AnimeLibrary"))
|
||||
BrowseTab(ItemType.anime, BrowseTabKind.sources),
|
||||
if (!hideItems.contains("/NovelLibrary"))
|
||||
BrowseTab(ItemType.novel, BrowseTabKind.sources),
|
||||
|
||||
if (!hideItems.contains("/MangaLibrary"))
|
||||
BrowseTab(ItemType.manga, BrowseTabKind.extensions),
|
||||
if (!hideItems.contains("/AnimeLibrary"))
|
||||
BrowseTab(ItemType.anime, BrowseTabKind.extensions),
|
||||
if (!hideItems.contains("/NovelLibrary"))
|
||||
BrowseTab(ItemType.novel, BrowseTabKind.extensions),
|
||||
];
|
||||
|
||||
@override
|
||||
|
|
@ -65,11 +82,8 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
|||
if (_tabList.isEmpty) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
final containsExtensionTab = [
|
||||
"mangaExtension",
|
||||
"animeExtension",
|
||||
"novelExtension",
|
||||
].any((element) => _tabList[_tabBarController.index] == element);
|
||||
final currentTab = _tabList[_tabBarController.index];
|
||||
final isExtensionTab = currentTab.kind == BrowseTabKind.extensions;
|
||||
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return DefaultTabController(
|
||||
|
|
@ -102,9 +116,7 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
|||
)
|
||||
: Row(
|
||||
children: [
|
||||
if (_tabBarController.index == 3 ||
|
||||
_tabBarController.index == 4 ||
|
||||
_tabBarController.index == 5)
|
||||
if (isExtensionTab)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.push('/createExtension');
|
||||
|
|
@ -117,26 +129,19 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
|||
IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {
|
||||
if (containsExtensionTab) {
|
||||
if (isExtensionTab) {
|
||||
setState(() {
|
||||
_isSearch = true;
|
||||
});
|
||||
} else {
|
||||
context.push(
|
||||
'/globalSearch',
|
||||
extra: (
|
||||
null,
|
||||
switch (_tabList[_tabBarController.index]) {
|
||||
"manga" => ItemType.manga,
|
||||
"anime" => ItemType.anime,
|
||||
_ => ItemType.novel,
|
||||
},
|
||||
),
|
||||
extra: (null, currentTab.type),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
!containsExtensionTab
|
||||
!isExtensionTab
|
||||
? Icons.travel_explore_rounded
|
||||
: Icons.search_rounded,
|
||||
color: Theme.of(context).hintColor,
|
||||
|
|
@ -148,16 +153,12 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
|||
splashRadius: 20,
|
||||
onPressed: () {
|
||||
context.push(
|
||||
containsExtensionTab ? '/ExtensionLang' : '/sourceFilter',
|
||||
extra: switch (_tabList[_tabBarController.index]) {
|
||||
"manga" || "mangaExtension" => ItemType.manga,
|
||||
"anime" || "animeExtension" => ItemType.anime,
|
||||
_ => ItemType.novel,
|
||||
},
|
||||
isExtensionTab ? '/ExtensionLang' : '/sourceFilter',
|
||||
extra: currentTab.type,
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
!containsExtensionTab
|
||||
!isExtensionTab
|
||||
? Icons.filter_list_sharp
|
||||
: Icons.translate_rounded,
|
||||
color: Theme.of(context).hintColor,
|
||||
|
|
@ -168,86 +169,44 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
|||
indicatorSize: TabBarIndicatorSize.label,
|
||||
isScrollable: true,
|
||||
controller: _tabBarController,
|
||||
tabs: [
|
||||
if (!hideItems.contains("/MangaLibrary"))
|
||||
Tab(text: l10n.manga_sources),
|
||||
if (!hideItems.contains("/AnimeLibrary"))
|
||||
Tab(text: l10n.anime_sources),
|
||||
if (!hideItems.contains("/NovelLibrary"))
|
||||
Tab(text: l10n.novel_sources),
|
||||
if (!hideItems.contains("/MangaLibrary"))
|
||||
Tab(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.manga_extensions),
|
||||
tabs: _tabList.map((tab) {
|
||||
final type = tab.type;
|
||||
final isExt = tab.kind == BrowseTabKind.extensions;
|
||||
|
||||
return Tab(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
isExt
|
||||
? type.localizedExtensions(l10n)
|
||||
: type.localizedSources(l10n),
|
||||
),
|
||||
if (isExt) ...[
|
||||
const SizedBox(width: 8),
|
||||
_extensionUpdateNumbers(ref, ItemType.manga),
|
||||
_extensionUpdateNumbers(ref, type),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!hideItems.contains("/AnimeLibrary"))
|
||||
Tab(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.anime_extensions),
|
||||
const SizedBox(width: 8),
|
||||
_extensionUpdateNumbers(ref, ItemType.anime),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!hideItems.contains("/NovelLibrary"))
|
||||
Tab(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.novel_extensions),
|
||||
const SizedBox(width: 8),
|
||||
_extensionUpdateNumbers(ref, ItemType.novel),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabBarController,
|
||||
children: [
|
||||
if (!hideItems.contains("/MangaLibrary"))
|
||||
SourcesScreen(
|
||||
itemType: ItemType.manga,
|
||||
tabIndex: (index) {
|
||||
_tabBarController.animateTo(index);
|
||||
},
|
||||
),
|
||||
if (!hideItems.contains("/AnimeLibrary"))
|
||||
SourcesScreen(
|
||||
itemType: ItemType.anime,
|
||||
tabIndex: (index) {
|
||||
_tabBarController.animateTo(index);
|
||||
},
|
||||
),
|
||||
if (!hideItems.contains("/NovelLibrary"))
|
||||
SourcesScreen(
|
||||
itemType: ItemType.novel,
|
||||
tabIndex: (index) {
|
||||
_tabBarController.animateTo(index);
|
||||
},
|
||||
),
|
||||
if (!hideItems.contains("/MangaLibrary"))
|
||||
ExtensionScreen(
|
||||
children: _tabList.map((tab) {
|
||||
if (tab.kind == BrowseTabKind.sources) {
|
||||
return SourcesScreen(
|
||||
itemType: tab.type,
|
||||
tabs: _tabList,
|
||||
tabIndex: (index) => _tabBarController.animateTo(index),
|
||||
);
|
||||
} else {
|
||||
return ExtensionScreen(
|
||||
query: _textEditingController.text,
|
||||
itemType: ItemType.manga,
|
||||
),
|
||||
if (!hideItems.contains("/AnimeLibrary"))
|
||||
ExtensionScreen(
|
||||
query: _textEditingController.text,
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
if (!hideItems.contains("/NovelLibrary"))
|
||||
ExtensionScreen(
|
||||
query: _textEditingController.text,
|
||||
itemType: ItemType.novel,
|
||||
),
|
||||
],
|
||||
itemType: tab.type,
|
||||
);
|
||||
}
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -30,6 +31,12 @@ class _ExtensionDetailState extends ConsumerState<ExtensionDetail> {
|
|||
late Source source = isar.sources.getSync(widget.source.id!)!;
|
||||
late List<SourcePreference>? sourcePreference = () {
|
||||
try {
|
||||
if (source.sourceCodeLanguage == SourceCodeLanguage.mihon &&
|
||||
source.preferenceList != null) {
|
||||
return (jsonDecode(source.preferenceList!) as List)
|
||||
.map((e) => SourcePreference.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
return getSourcePreference(
|
||||
source: source,
|
||||
).map((e) => getSourcePreferenceEntry(e.key!, source.id!)).toList();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'extensions_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(getExtensionsStream)
|
||||
const getExtensionsStreamProvider = GetExtensionsStreamFamily._();
|
||||
final getExtensionsStreamProvider = GetExtensionsStreamFamily._();
|
||||
|
||||
final class GetExtensionsStreamProvider
|
||||
extends
|
||||
|
|
@ -20,7 +20,7 @@ final class GetExtensionsStreamProvider
|
|||
Stream<List<Source>>
|
||||
>
|
||||
with $FutureModifier<List<Source>>, $StreamProvider<List<Source>> {
|
||||
const GetExtensionsStreamProvider._({
|
||||
GetExtensionsStreamProvider._({
|
||||
required GetExtensionsStreamFamily super.from,
|
||||
required ItemType super.argument,
|
||||
}) : super(
|
||||
|
|
@ -69,7 +69,7 @@ String _$getExtensionsStreamHash() =>
|
|||
|
||||
final class GetExtensionsStreamFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<List<Source>>, ItemType> {
|
||||
const GetExtensionsStreamFamily._()
|
||||
GetExtensionsStreamFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getExtensionsStreamProvider',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/browse/browse_screen.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_sliver_grouped_list_view.dart';
|
||||
import 'package:isar_community/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
|
|
@ -11,10 +12,12 @@ import 'package:mangayomi/utils/language.dart';
|
|||
|
||||
class SourcesScreen extends ConsumerStatefulWidget {
|
||||
final Function(int) tabIndex;
|
||||
final List<BrowseTab> tabs;
|
||||
final ItemType itemType;
|
||||
const SourcesScreen({
|
||||
required this.tabIndex,
|
||||
required this.itemType,
|
||||
required this.tabs,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -62,13 +65,17 @@ class _SourcesScreenState extends ConsumerState<SourcesScreen> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => widget.tabIndex(
|
||||
widget.itemType == ItemType.manga
|
||||
? 3
|
||||
: widget.itemType == ItemType.anime
|
||||
? 4
|
||||
: 5,
|
||||
),
|
||||
onPressed: () {
|
||||
final extensionIndex = widget.tabs.indexWhere(
|
||||
(t) =>
|
||||
t.type == widget.itemType &&
|
||||
t.kind == BrowseTabKind.extensions,
|
||||
);
|
||||
|
||||
if (extensionIndex != -1) {
|
||||
widget.tabIndex(extensionIndex);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.extension_rounded),
|
||||
label: Text(context.l10n.show_extensions),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:mangayomi/models/source.dart';
|
|||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/cached_network.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/utils/item_type_localization.dart';
|
||||
import 'package:mangayomi/utils/language.dart';
|
||||
|
||||
class SourceListTile extends StatelessWidget {
|
||||
|
|
@ -81,11 +82,7 @@ class SourceListTile extends StatelessWidget {
|
|||
title: Text(
|
||||
!isLocal
|
||||
? source.name!
|
||||
: "${context.l10n.local_source} ${source.itemType == ItemType.manga
|
||||
? context.l10n.manga
|
||||
: source.itemType == ItemType.anime
|
||||
? context.l10n.anime
|
||||
: context.l10n.novel}",
|
||||
: "${context.l10n.local_source} ${source.itemType.localized(context.l10n)}",
|
||||
),
|
||||
trailing: SizedBox(
|
||||
width: 150,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:isar_community/isar.dart';
|
|||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/modules/calendar/providers/calendar_provider.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/modules/widgets/custom_sliver_grouped_list_view.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
|
|
@ -15,6 +16,8 @@ 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:mangayomi/utils/item_type_filters.dart';
|
||||
import 'package:mangayomi/utils/item_type_localization.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
class CalendarScreen extends ConsumerStatefulWidget {
|
||||
|
|
@ -35,11 +38,19 @@ class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|||
DateTime? _selectedDay;
|
||||
DateTime? _rangeStart;
|
||||
DateTime? _rangeEnd;
|
||||
late ItemType? itemType = widget.itemType ?? ItemType.manga;
|
||||
late ItemType? itemType;
|
||||
late List<ItemType> _visibleTypes;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_visibleTypes = hiddenItemTypes(ref.read(hideItemsStateProvider));
|
||||
final initialItemType = widget.itemType ?? ItemType.manga;
|
||||
if (_visibleTypes.contains(initialItemType)) {
|
||||
itemType = initialItemType;
|
||||
} else {
|
||||
itemType = _visibleTypes.isNotEmpty ? _visibleTypes.first : null;
|
||||
}
|
||||
_selectedDay = _focusedDay;
|
||||
_selectedEntries = ValueNotifier([]);
|
||||
}
|
||||
|
|
@ -69,31 +80,7 @@ class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildWarningTile(context),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
|
|
@ -107,29 +94,15 @@ class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
),
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: ItemType.manga.index,
|
||||
segments: _visibleTypes.map((type) {
|
||||
return ButtonSegment(
|
||||
value: type.index,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(l10n.manga),
|
||||
child: Text(type.localized(l10n)),
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
selected: {itemType?.index},
|
||||
onSelectionChanged: (newSelection) {
|
||||
if (newSelection.isNotEmpty &&
|
||||
|
|
@ -145,40 +118,7 @@ class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|||
],
|
||||
),
|
||||
),
|
||||
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;
|
||||
},
|
||||
),
|
||||
_buildCalendar(data, locale),
|
||||
const SizedBox(height: 15),
|
||||
],
|
||||
),
|
||||
|
|
@ -241,8 +181,64 @@ class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildWarningTile(BuildContext context) {
|
||||
return 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(
|
||||
context.l10n.calendar_info,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(fontSize: 13, color: context.secondaryColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCalendar(List<Manga> data, Locale locale) {
|
||||
return 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,
|
||||
);
|
||||
}
|
||||
|
||||
final Map<String, List<Manga>> _dayCache = {};
|
||||
|
||||
List<Manga> _getEntriesForDay(DateTime day, List<Manga> data) {
|
||||
return data.where((e) {
|
||||
final key = "${day.year}-${day.month}-${day.day}";
|
||||
if (_dayCache.containsKey(key)) return _dayCache[key]!;
|
||||
final result = data.where((e) {
|
||||
final lastChapter = e.chapters
|
||||
.filter()
|
||||
.sortByDateUploadDesc()
|
||||
|
|
@ -252,10 +248,12 @@ class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|||
? DateTime.fromMillisecondsSinceEpoch(lastDate)
|
||||
: DateTime.now();
|
||||
final temp = start.add(Duration(days: e.smartUpdateDays!));
|
||||
final predictedDay = "${temp.year}-${temp.month}-${temp.day}";
|
||||
final selectedDay = "${day.year}-${day.month}-${day.day}";
|
||||
return predictedDay == selectedDay;
|
||||
return temp.year == day.year &&
|
||||
temp.month == day.month &&
|
||||
temp.day == day.day;
|
||||
}).toList();
|
||||
_dayCache[key] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Manga> _getEntriesForRange(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'calendar_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(getCalendarStream)
|
||||
const getCalendarStreamProvider = GetCalendarStreamFamily._();
|
||||
final getCalendarStreamProvider = GetCalendarStreamFamily._();
|
||||
|
||||
final class GetCalendarStreamProvider
|
||||
extends
|
||||
|
|
@ -20,7 +20,7 @@ final class GetCalendarStreamProvider
|
|||
Stream<List<Manga>>
|
||||
>
|
||||
with $FutureModifier<List<Manga>>, $StreamProvider<List<Manga>> {
|
||||
const GetCalendarStreamProvider._({
|
||||
GetCalendarStreamProvider._({
|
||||
required GetCalendarStreamFamily super.from,
|
||||
required ItemType? super.argument,
|
||||
}) : super(
|
||||
|
|
@ -68,7 +68,7 @@ String _$getCalendarStreamHash() => r'850d81742f8ac5ce88175732c0edf57a7a9295d4';
|
|||
|
||||
final class GetCalendarStreamFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<List<Manga>>, ItemType?> {
|
||||
const GetCalendarStreamFamily._()
|
||||
GetCalendarStreamFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getCalendarStreamProvider',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mangayomi/l10n/generated/app_localizations.dart';
|
||||
import 'package:mangayomi/modules/widgets/base_library_tab_screen.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_sliver_grouped_list_view.dart';
|
||||
|
||||
import 'package:isar_community/isar.dart';
|
||||
|
|
@ -12,7 +13,6 @@ import 'package:mangayomi/models/chapter.dart';
|
|||
import 'package:mangayomi/models/history.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/modules/history/providers/isar_providers.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/cached_network.dart';
|
||||
|
|
@ -20,7 +20,6 @@ import 'package:mangayomi/utils/constant.dart';
|
|||
import 'package:mangayomi/utils/date.dart';
|
||||
import 'package:mangayomi/utils/extensions/chapter.dart';
|
||||
import 'package:mangayomi/utils/headers.dart';
|
||||
import 'package:mangayomi/modules/library/widgets/search_text_form_field.dart';
|
||||
import 'package:mangayomi/modules/widgets/error_text.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
|
||||
|
|
@ -31,175 +30,61 @@ class HistoryScreen extends ConsumerStatefulWidget {
|
|||
ConsumerState<HistoryScreen> createState() => _HistoryScreenState();
|
||||
}
|
||||
|
||||
class _HistoryScreenState extends ConsumerState<HistoryScreen>
|
||||
with TickerProviderStateMixin {
|
||||
final _textEditingController = TextEditingController();
|
||||
late TabController _tabBarController;
|
||||
class _HistoryScreenState extends BaseLibraryTabScreenState<HistoryScreen> {
|
||||
@override
|
||||
String get title => l10nLocalizations(context)!.history;
|
||||
|
||||
void tabListener() {
|
||||
setState(() {
|
||||
_textEditingController.clear();
|
||||
_isSearch = false;
|
||||
});
|
||||
@override
|
||||
Widget buildTab(ItemType type) {
|
||||
return HistoryTab(itemType: type, query: textEditingController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final hideItems = ref.read(hideItemsStateProvider);
|
||||
final tabCount = [
|
||||
if (!hideItems.contains("/MangaLibrary")) "/MangaLibrary",
|
||||
if (!hideItems.contains("/AnimeLibrary")) "/AnimeLibrary",
|
||||
if (!hideItems.contains("/NovelLibrary")) "/NovelLibrary",
|
||||
].length;
|
||||
_tabBarController = TabController(length: tabCount, vsync: this);
|
||||
_tabBarController.addListener(tabListener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabBarController.dispose();
|
||||
_textEditingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool _isSearch = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hideItems = ref.watch(hideItemsStateProvider);
|
||||
List<Widget> buildExtraActions(BuildContext context) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: _isSearch
|
||||
? null
|
||||
: Text(
|
||||
l10n.history,
|
||||
style: TextStyle(color: Theme.of(context).hintColor),
|
||||
),
|
||||
actions: [
|
||||
_isSearch
|
||||
? SeachFormTextField(
|
||||
onChanged: (value) {
|
||||
setState(() {});
|
||||
},
|
||||
onSuffixPressed: () {
|
||||
_textEditingController.clear();
|
||||
setState(() {});
|
||||
},
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isSearch = false;
|
||||
});
|
||||
_textEditingController.clear();
|
||||
},
|
||||
controller: _textEditingController,
|
||||
)
|
||||
: IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isSearch = true;
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.search, color: Theme.of(context).hintColor),
|
||||
),
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.remove_everything),
|
||||
content: Text(l10n.remove_everything_msg),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (mounted) Navigator.pop(context);
|
||||
await _clearHistory(hideItems);
|
||||
},
|
||||
child: Text(l10n.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.delete_sweep_outlined,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
bottom: TabBar(
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
controller: _tabBarController,
|
||||
tabs: [
|
||||
if (!hideItems.contains("/MangaLibrary")) Tab(text: l10n.manga),
|
||||
if (!hideItems.contains("/AnimeLibrary")) Tab(text: l10n.anime),
|
||||
if (!hideItems.contains("/NovelLibrary")) Tab(text: l10n.novel),
|
||||
],
|
||||
|
||||
return [
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
icon: Icon(
|
||||
Icons.delete_sweep_outlined,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(l10n.remove_everything),
|
||||
content: Text(l10n.remove_everything_msg),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(dialogContext).pop();
|
||||
await _clearHistory();
|
||||
},
|
||||
child: Text(l10n.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabBarController,
|
||||
children: [
|
||||
if (!hideItems.contains("/MangaLibrary"))
|
||||
HistoryTab(
|
||||
itemType: ItemType.manga,
|
||||
query: _textEditingController.text,
|
||||
),
|
||||
if (!hideItems.contains("/AnimeLibrary"))
|
||||
HistoryTab(
|
||||
itemType: ItemType.anime,
|
||||
query: _textEditingController.text,
|
||||
),
|
||||
if (!hideItems.contains("/NovelLibrary"))
|
||||
HistoryTab(
|
||||
itemType: ItemType.novel,
|
||||
query: _textEditingController.text,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
Future<void> _clearHistory(List<String> hideItems) async {
|
||||
Future<void> _clearHistory() async {
|
||||
List<History> histories = await isar.historys
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.chapter(
|
||||
(q) =>
|
||||
q.manga((q) => q.itemTypeEqualTo(getCurrentItemType(hideItems))),
|
||||
)
|
||||
.chapter((q) => q.manga((q) => q.itemTypeEqualTo(getCurrentItemType())))
|
||||
.findAll();
|
||||
final List<Id> idsToDelete = histories.map((h) => h.id!).toList();
|
||||
await isar.writeTxn(() => isar.historys.deleteAll(idsToDelete));
|
||||
}
|
||||
|
||||
ItemType getCurrentItemType(List<String> hideItems) {
|
||||
return _tabBarController.index == 0 && !hideItems.contains("/MangaLibrary")
|
||||
? ItemType.manga
|
||||
: _tabBarController.index ==
|
||||
1 - (hideItems.contains("/MangaLibrary") ? 1 : 0) &&
|
||||
!hideItems.contains("/AnimeLibrary")
|
||||
? ItemType.anime
|
||||
: ItemType.novel;
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryTab extends ConsumerStatefulWidget {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'isar_providers.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(getAllHistoryStream)
|
||||
const getAllHistoryStreamProvider = GetAllHistoryStreamFamily._();
|
||||
final getAllHistoryStreamProvider = GetAllHistoryStreamFamily._();
|
||||
|
||||
final class GetAllHistoryStreamProvider
|
||||
extends
|
||||
|
|
@ -20,7 +20,7 @@ final class GetAllHistoryStreamProvider
|
|||
Stream<List<History>>
|
||||
>
|
||||
with $FutureModifier<List<History>>, $StreamProvider<List<History>> {
|
||||
const GetAllHistoryStreamProvider._({
|
||||
GetAllHistoryStreamProvider._({
|
||||
required GetAllHistoryStreamFamily super.from,
|
||||
required ({ItemType itemType, String search}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -77,7 +77,7 @@ final class GetAllHistoryStreamFamily extends $Family
|
|||
Stream<List<History>>,
|
||||
({ItemType itemType, String search})
|
||||
> {
|
||||
const GetAllHistoryStreamFamily._()
|
||||
GetAllHistoryStreamFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getAllHistoryStreamProvider',
|
||||
|
|
@ -99,7 +99,7 @@ final class GetAllHistoryStreamFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(getAllUpdateStream)
|
||||
const getAllUpdateStreamProvider = GetAllUpdateStreamFamily._();
|
||||
final getAllUpdateStreamProvider = GetAllUpdateStreamFamily._();
|
||||
|
||||
final class GetAllUpdateStreamProvider
|
||||
extends
|
||||
|
|
@ -109,7 +109,7 @@ final class GetAllUpdateStreamProvider
|
|||
Stream<List<Update>>
|
||||
>
|
||||
with $FutureModifier<List<Update>>, $StreamProvider<List<Update>> {
|
||||
const GetAllUpdateStreamProvider._({
|
||||
GetAllUpdateStreamProvider._({
|
||||
required GetAllUpdateStreamFamily super.from,
|
||||
required ({ItemType itemType, String search}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -166,7 +166,7 @@ final class GetAllUpdateStreamFamily extends $Family
|
|||
Stream<List<Update>>,
|
||||
({ItemType itemType, String search})
|
||||
> {
|
||||
const GetAllUpdateStreamFamily._()
|
||||
GetAllUpdateStreamFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getAllUpdateStreamProvider',
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar_community/isar.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/changed.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
|
|
@ -19,10 +17,8 @@ import 'package:mangayomi/models/update.dart';
|
|||
import 'package:mangayomi/modules/library/providers/add_torrent.dart';
|
||||
import 'package:mangayomi/modules/library/providers/local_archive.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart';
|
||||
import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart';
|
||||
import 'package:mangayomi/modules/more/providers/downloaded_only_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
|
||||
import 'package:mangayomi/modules/widgets/bottom_select_bar.dart';
|
||||
import 'package:mangayomi/modules/widgets/category_selection_dialog.dart';
|
||||
|
|
@ -30,6 +26,7 @@ import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
|
|||
import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/services/library_updater.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/modules/library/providers/isar_providers.dart';
|
||||
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
|
||||
|
|
@ -42,6 +39,7 @@ import 'package:mangayomi/modules/widgets/error_text.dart';
|
|||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
import 'package:mangayomi/utils/global_style.dart';
|
||||
import 'package:mangayomi/utils/item_type_localization.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
|
|
@ -82,53 +80,6 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _updateLibrary(List<Manga> mangaList) async {
|
||||
bool isDark = ref.read(themeModeStateProvider);
|
||||
botToast(
|
||||
context.l10n.updating_library("0", "0", "0"),
|
||||
fontSize: 13,
|
||||
second: 30,
|
||||
alignY: !context.isTablet ? 0.85 : 1,
|
||||
themeDark: isDark,
|
||||
);
|
||||
int numbers = 0;
|
||||
int failed = 0;
|
||||
for (var manga in mangaList) {
|
||||
try {
|
||||
await ref.read(
|
||||
updateMangaDetailProvider(
|
||||
mangaId: manga.id,
|
||||
isInit: false,
|
||||
showToast: false,
|
||||
).future,
|
||||
);
|
||||
} catch (_) {
|
||||
failed++;
|
||||
}
|
||||
numbers++;
|
||||
if (mounted) {
|
||||
botToast(
|
||||
context.l10n.updating_library(numbers, failed, mangaList.length),
|
||||
fontSize: 13,
|
||||
second: 10,
|
||||
alignY: !context.isTablet ? 0.85 : 1,
|
||||
animationDuration: 0,
|
||||
dismissDirections: [DismissDirection.none],
|
||||
onlyOne: false,
|
||||
themeDark: isDark,
|
||||
);
|
||||
}
|
||||
}
|
||||
await Future.doWhile(() async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (mangaList.length == numbers) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
BotToast.cleanAll();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final settingsStream = ref.watch(getSettingsStreamProvider);
|
||||
|
|
@ -776,7 +727,12 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
|||
final entriesManga = reverse ? entries.reversed.toList() : entries;
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _updateLibrary(data);
|
||||
await updateLibrary(
|
||||
ref: ref,
|
||||
context: context,
|
||||
mangaList: data,
|
||||
itemType: widget.itemType,
|
||||
);
|
||||
},
|
||||
child: displayType == DisplayType.list
|
||||
? LibraryListViewWidget(
|
||||
|
|
@ -867,7 +823,12 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
|||
final entriesManga = reverse ? entries.reversed.toList() : entries;
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _updateLibrary(data);
|
||||
await updateLibrary(
|
||||
ref: ref,
|
||||
context: context,
|
||||
mangaList: data,
|
||||
itemType: widget.itemType,
|
||||
);
|
||||
},
|
||||
child: displayType == DisplayType.list
|
||||
? LibraryListViewWidget(
|
||||
|
|
@ -1901,11 +1862,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
|||
: Row(
|
||||
children: [
|
||||
Text(
|
||||
widget.itemType == ItemType.manga
|
||||
? l10n.manga
|
||||
: widget.itemType == ItemType.anime
|
||||
? l10n.anime
|
||||
: l10n.novel,
|
||||
widget.itemType.localized(l10n),
|
||||
style: TextStyle(color: Theme.of(context).hintColor),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
|
|
@ -2013,7 +1970,12 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
|||
onSelected: (value) {
|
||||
if (value == 0) {
|
||||
manga.whenData((value) {
|
||||
_updateLibrary(value);
|
||||
updateLibrary(
|
||||
ref: ref,
|
||||
context: context,
|
||||
mangaList: value,
|
||||
itemType: widget.itemType,
|
||||
);
|
||||
});
|
||||
} else if (value == 1) {
|
||||
manga.whenData((value) {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ part of 'add_torrent.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(addTorrentFromUrlOrFromFile)
|
||||
const addTorrentFromUrlOrFromFileProvider =
|
||||
final addTorrentFromUrlOrFromFileProvider =
|
||||
AddTorrentFromUrlOrFromFileFamily._();
|
||||
|
||||
final class AddTorrentFromUrlOrFromFileProvider
|
||||
extends $FunctionalProvider<AsyncValue<dynamic>, dynamic, FutureOr<dynamic>>
|
||||
with $FutureModifier<dynamic>, $FutureProvider<dynamic> {
|
||||
const AddTorrentFromUrlOrFromFileProvider._({
|
||||
AddTorrentFromUrlOrFromFileProvider._({
|
||||
required AddTorrentFromUrlOrFromFileFamily super.from,
|
||||
required (Manga?, {bool init, String? url}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -74,7 +74,7 @@ final class AddTorrentFromUrlOrFromFileFamily extends $Family
|
|||
FutureOr<dynamic>,
|
||||
(Manga?, {bool init, String? url})
|
||||
> {
|
||||
const AddTorrentFromUrlOrFromFileFamily._()
|
||||
AddTorrentFromUrlOrFromFileFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'addTorrentFromUrlOrFromFileProvider',
|
||||
|
|
|
|||
|
|
@ -306,20 +306,16 @@ Future<void> _scanDirectory(Ref ref, Directory? dir) async {
|
|||
: Uint8List.fromList(coverImage).getCoverImage;
|
||||
saveManga++;
|
||||
}
|
||||
for (var chapter in book.Chapters ?? []) {
|
||||
chaptersToSave.add(
|
||||
Chapter(
|
||||
mangaId: manga.id,
|
||||
name: chapter.Title is String && chapter.Title.isEmpty
|
||||
? "Book"
|
||||
: chapter.Title,
|
||||
archivePath: chapterPath,
|
||||
downloadSize: chapterFile.existsSync()
|
||||
? chapterFile.lengthSync().formattedFileSize()
|
||||
: null,
|
||||
)..manga.value = manga,
|
||||
);
|
||||
}
|
||||
chaptersToSave.add(
|
||||
Chapter(
|
||||
mangaId: manga.id,
|
||||
name: book.Title,
|
||||
archivePath: chapterPath,
|
||||
downloadSize: chapterFile.existsSync()
|
||||
? chapterFile.lengthSync().formattedFileSize()
|
||||
: null,
|
||||
)..manga.value = manga,
|
||||
);
|
||||
} else {
|
||||
final chap = Chapter(
|
||||
mangaId: manga.id,
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ part of 'file_scanner.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(LocalFoldersState)
|
||||
const localFoldersStateProvider = LocalFoldersStateProvider._();
|
||||
final localFoldersStateProvider = LocalFoldersStateProvider._();
|
||||
|
||||
final class LocalFoldersStateProvider
|
||||
extends $NotifierProvider<LocalFoldersState, List<String>> {
|
||||
const LocalFoldersStateProvider._()
|
||||
LocalFoldersStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -48,7 +48,6 @@ abstract class _$LocalFoldersState extends $Notifier<List<String>> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<List<String>, List<String>>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -58,7 +57,7 @@ abstract class _$LocalFoldersState extends $Notifier<List<String>> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +80,7 @@ abstract class _$LocalFoldersState extends $Notifier<List<String>> {
|
|||
/// ```
|
||||
|
||||
@ProviderFor(scanLocalLibrary)
|
||||
const scanLocalLibraryProvider = ScanLocalLibraryProvider._();
|
||||
final scanLocalLibraryProvider = ScanLocalLibraryProvider._();
|
||||
|
||||
/// Scans `Mangayomi/local` folder (if exists) for Mangas/Animes and imports in library.
|
||||
///
|
||||
|
|
@ -121,7 +120,7 @@ final class ScanLocalLibraryProvider
|
|||
/// Archivetypes: cbz, zip, cbt, tar
|
||||
/// Other types: epub
|
||||
/// ```
|
||||
const ScanLocalLibraryProvider._()
|
||||
ScanLocalLibraryProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'isar_providers.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(getAllMangaStream)
|
||||
const getAllMangaStreamProvider = GetAllMangaStreamFamily._();
|
||||
final getAllMangaStreamProvider = GetAllMangaStreamFamily._();
|
||||
|
||||
final class GetAllMangaStreamProvider
|
||||
extends
|
||||
|
|
@ -20,7 +20,7 @@ final class GetAllMangaStreamProvider
|
|||
Stream<List<Manga>>
|
||||
>
|
||||
with $FutureModifier<List<Manga>>, $StreamProvider<List<Manga>> {
|
||||
const GetAllMangaStreamProvider._({
|
||||
GetAllMangaStreamProvider._({
|
||||
required GetAllMangaStreamFamily super.from,
|
||||
required ({int? categoryId, ItemType itemType}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -76,7 +76,7 @@ final class GetAllMangaStreamFamily extends $Family
|
|||
Stream<List<Manga>>,
|
||||
({int? categoryId, ItemType itemType})
|
||||
> {
|
||||
const GetAllMangaStreamFamily._()
|
||||
GetAllMangaStreamFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getAllMangaStreamProvider',
|
||||
|
|
@ -98,7 +98,7 @@ final class GetAllMangaStreamFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(getAllMangaWithoutCategoriesStream)
|
||||
const getAllMangaWithoutCategoriesStreamProvider =
|
||||
final getAllMangaWithoutCategoriesStreamProvider =
|
||||
GetAllMangaWithoutCategoriesStreamFamily._();
|
||||
|
||||
final class GetAllMangaWithoutCategoriesStreamProvider
|
||||
|
|
@ -109,7 +109,7 @@ final class GetAllMangaWithoutCategoriesStreamProvider
|
|||
Stream<List<Manga>>
|
||||
>
|
||||
with $FutureModifier<List<Manga>>, $StreamProvider<List<Manga>> {
|
||||
const GetAllMangaWithoutCategoriesStreamProvider._({
|
||||
GetAllMangaWithoutCategoriesStreamProvider._({
|
||||
required GetAllMangaWithoutCategoriesStreamFamily super.from,
|
||||
required ItemType super.argument,
|
||||
}) : super(
|
||||
|
|
@ -160,7 +160,7 @@ String _$getAllMangaWithoutCategoriesStreamHash() =>
|
|||
|
||||
final class GetAllMangaWithoutCategoriesStreamFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<List<Manga>>, ItemType> {
|
||||
const GetAllMangaWithoutCategoriesStreamFamily._()
|
||||
GetAllMangaWithoutCategoriesStreamFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getAllMangaWithoutCategoriesStreamProvider',
|
||||
|
|
@ -181,7 +181,7 @@ final class GetAllMangaWithoutCategoriesStreamFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(getSettingsStream)
|
||||
const getSettingsStreamProvider = GetSettingsStreamProvider._();
|
||||
final getSettingsStreamProvider = GetSettingsStreamProvider._();
|
||||
|
||||
final class GetSettingsStreamProvider
|
||||
extends
|
||||
|
|
@ -191,7 +191,7 @@ final class GetSettingsStreamProvider
|
|||
Stream<List<Settings>>
|
||||
>
|
||||
with $FutureModifier<List<Settings>>, $StreamProvider<List<Settings>> {
|
||||
const GetSettingsStreamProvider._()
|
||||
GetSettingsStreamProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ part of 'library_state_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(LibraryDisplayTypeState)
|
||||
const libraryDisplayTypeStateProvider = LibraryDisplayTypeStateFamily._();
|
||||
final libraryDisplayTypeStateProvider = LibraryDisplayTypeStateFamily._();
|
||||
|
||||
final class LibraryDisplayTypeStateProvider
|
||||
extends $NotifierProvider<LibraryDisplayTypeState, DisplayType> {
|
||||
const LibraryDisplayTypeStateProvider._({
|
||||
LibraryDisplayTypeStateProvider._({
|
||||
required LibraryDisplayTypeStateFamily super.from,
|
||||
required ({ItemType itemType, Settings settings}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -71,7 +71,7 @@ final class LibraryDisplayTypeStateFamily extends $Family
|
|||
DisplayType,
|
||||
({ItemType itemType, Settings settings})
|
||||
> {
|
||||
const LibraryDisplayTypeStateFamily._()
|
||||
LibraryDisplayTypeStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'libraryDisplayTypeStateProvider',
|
||||
|
|
@ -101,7 +101,6 @@ abstract class _$LibraryDisplayTypeState extends $Notifier<DisplayType> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args.itemType, settings: _$args.settings);
|
||||
final ref = this.ref as $Ref<DisplayType, DisplayType>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -111,16 +110,19 @@ abstract class _$LibraryDisplayTypeState extends $Notifier<DisplayType> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(itemType: _$args.itemType, settings: _$args.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(LibraryGridSizeState)
|
||||
const libraryGridSizeStateProvider = LibraryGridSizeStateFamily._();
|
||||
final libraryGridSizeStateProvider = LibraryGridSizeStateFamily._();
|
||||
|
||||
final class LibraryGridSizeStateProvider
|
||||
extends $NotifierProvider<LibraryGridSizeState, int?> {
|
||||
const LibraryGridSizeStateProvider._({
|
||||
LibraryGridSizeStateProvider._({
|
||||
required LibraryGridSizeStateFamily super.from,
|
||||
required ItemType super.argument,
|
||||
}) : super(
|
||||
|
|
@ -170,7 +172,7 @@ String _$libraryGridSizeStateHash() =>
|
|||
final class LibraryGridSizeStateFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<LibraryGridSizeState, int?, int?, int?, ItemType> {
|
||||
const LibraryGridSizeStateFamily._()
|
||||
LibraryGridSizeStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'libraryGridSizeStateProvider',
|
||||
|
|
@ -194,7 +196,6 @@ abstract class _$LibraryGridSizeState extends $Notifier<int?> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args);
|
||||
final ref = this.ref as $Ref<int?, int?>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -204,16 +205,16 @@ abstract class _$LibraryGridSizeState extends $Notifier<int?> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(itemType: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(MangaFilterDownloadedState)
|
||||
const mangaFilterDownloadedStateProvider = MangaFilterDownloadedStateFamily._();
|
||||
final mangaFilterDownloadedStateProvider = MangaFilterDownloadedStateFamily._();
|
||||
|
||||
final class MangaFilterDownloadedStateProvider
|
||||
extends $NotifierProvider<MangaFilterDownloadedState, int> {
|
||||
const MangaFilterDownloadedStateProvider._({
|
||||
MangaFilterDownloadedStateProvider._({
|
||||
required MangaFilterDownloadedStateFamily super.from,
|
||||
required ({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
super.argument,
|
||||
|
|
@ -271,7 +272,7 @@ final class MangaFilterDownloadedStateFamily extends $Family
|
|||
int,
|
||||
({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
> {
|
||||
const MangaFilterDownloadedStateFamily._()
|
||||
MangaFilterDownloadedStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'mangaFilterDownloadedStateProvider',
|
||||
|
|
@ -309,11 +310,6 @@ abstract class _$MangaFilterDownloadedState extends $Notifier<int> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
);
|
||||
final ref = this.ref as $Ref<int, int>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -323,16 +319,23 @@ abstract class _$MangaFilterDownloadedState extends $Notifier<int> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(MangaFilterUnreadState)
|
||||
const mangaFilterUnreadStateProvider = MangaFilterUnreadStateFamily._();
|
||||
final mangaFilterUnreadStateProvider = MangaFilterUnreadStateFamily._();
|
||||
|
||||
final class MangaFilterUnreadStateProvider
|
||||
extends $NotifierProvider<MangaFilterUnreadState, int> {
|
||||
const MangaFilterUnreadStateProvider._({
|
||||
MangaFilterUnreadStateProvider._({
|
||||
required MangaFilterUnreadStateFamily super.from,
|
||||
required ({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
super.argument,
|
||||
|
|
@ -390,7 +393,7 @@ final class MangaFilterUnreadStateFamily extends $Family
|
|||
int,
|
||||
({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
> {
|
||||
const MangaFilterUnreadStateFamily._()
|
||||
MangaFilterUnreadStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'mangaFilterUnreadStateProvider',
|
||||
|
|
@ -428,11 +431,6 @@ abstract class _$MangaFilterUnreadState extends $Notifier<int> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
);
|
||||
final ref = this.ref as $Ref<int, int>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -442,16 +440,23 @@ abstract class _$MangaFilterUnreadState extends $Notifier<int> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(MangaFilterStartedState)
|
||||
const mangaFilterStartedStateProvider = MangaFilterStartedStateFamily._();
|
||||
final mangaFilterStartedStateProvider = MangaFilterStartedStateFamily._();
|
||||
|
||||
final class MangaFilterStartedStateProvider
|
||||
extends $NotifierProvider<MangaFilterStartedState, int> {
|
||||
const MangaFilterStartedStateProvider._({
|
||||
MangaFilterStartedStateProvider._({
|
||||
required MangaFilterStartedStateFamily super.from,
|
||||
required ({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
super.argument,
|
||||
|
|
@ -509,7 +514,7 @@ final class MangaFilterStartedStateFamily extends $Family
|
|||
int,
|
||||
({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
> {
|
||||
const MangaFilterStartedStateFamily._()
|
||||
MangaFilterStartedStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'mangaFilterStartedStateProvider',
|
||||
|
|
@ -547,11 +552,6 @@ abstract class _$MangaFilterStartedState extends $Notifier<int> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
);
|
||||
final ref = this.ref as $Ref<int, int>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -561,16 +561,23 @@ abstract class _$MangaFilterStartedState extends $Notifier<int> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(MangaFilterBookmarkedState)
|
||||
const mangaFilterBookmarkedStateProvider = MangaFilterBookmarkedStateFamily._();
|
||||
final mangaFilterBookmarkedStateProvider = MangaFilterBookmarkedStateFamily._();
|
||||
|
||||
final class MangaFilterBookmarkedStateProvider
|
||||
extends $NotifierProvider<MangaFilterBookmarkedState, int> {
|
||||
const MangaFilterBookmarkedStateProvider._({
|
||||
MangaFilterBookmarkedStateProvider._({
|
||||
required MangaFilterBookmarkedStateFamily super.from,
|
||||
required ({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
super.argument,
|
||||
|
|
@ -628,7 +635,7 @@ final class MangaFilterBookmarkedStateFamily extends $Family
|
|||
int,
|
||||
({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
> {
|
||||
const MangaFilterBookmarkedStateFamily._()
|
||||
MangaFilterBookmarkedStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'mangaFilterBookmarkedStateProvider',
|
||||
|
|
@ -666,11 +673,6 @@ abstract class _$MangaFilterBookmarkedState extends $Notifier<int> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
);
|
||||
final ref = this.ref as $Ref<int, int>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -680,16 +682,23 @@ abstract class _$MangaFilterBookmarkedState extends $Notifier<int> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(MangasFilterResultState)
|
||||
const mangasFilterResultStateProvider = MangasFilterResultStateFamily._();
|
||||
final mangasFilterResultStateProvider = MangasFilterResultStateFamily._();
|
||||
|
||||
final class MangasFilterResultStateProvider
|
||||
extends $NotifierProvider<MangasFilterResultState, bool> {
|
||||
const MangasFilterResultStateProvider._({
|
||||
MangasFilterResultStateProvider._({
|
||||
required MangasFilterResultStateFamily super.from,
|
||||
required ({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
super.argument,
|
||||
|
|
@ -747,7 +756,7 @@ final class MangasFilterResultStateFamily extends $Family
|
|||
bool,
|
||||
({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||
> {
|
||||
const MangasFilterResultStateFamily._()
|
||||
MangasFilterResultStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'mangasFilterResultStateProvider',
|
||||
|
|
@ -785,11 +794,6 @@ abstract class _$MangasFilterResultState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -799,17 +803,24 @@ abstract class _$MangasFilterResultState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(
|
||||
mangaList: _$args.mangaList,
|
||||
itemType: _$args.itemType,
|
||||
settings: _$args.settings,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(LibraryShowCategoryTabsState)
|
||||
const libraryShowCategoryTabsStateProvider =
|
||||
final libraryShowCategoryTabsStateProvider =
|
||||
LibraryShowCategoryTabsStateFamily._();
|
||||
|
||||
final class LibraryShowCategoryTabsStateProvider
|
||||
extends $NotifierProvider<LibraryShowCategoryTabsState, bool> {
|
||||
const LibraryShowCategoryTabsStateProvider._({
|
||||
LibraryShowCategoryTabsStateProvider._({
|
||||
required LibraryShowCategoryTabsStateFamily super.from,
|
||||
required ({ItemType itemType, Settings settings}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -866,7 +877,7 @@ final class LibraryShowCategoryTabsStateFamily extends $Family
|
|||
bool,
|
||||
({ItemType itemType, Settings settings})
|
||||
> {
|
||||
const LibraryShowCategoryTabsStateFamily._()
|
||||
LibraryShowCategoryTabsStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'libraryShowCategoryTabsStateProvider',
|
||||
|
|
@ -896,7 +907,6 @@ abstract class _$LibraryShowCategoryTabsState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args.itemType, settings: _$args.settings);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -906,17 +916,20 @@ abstract class _$LibraryShowCategoryTabsState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(itemType: _$args.itemType, settings: _$args.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(LibraryDownloadedChaptersState)
|
||||
const libraryDownloadedChaptersStateProvider =
|
||||
final libraryDownloadedChaptersStateProvider =
|
||||
LibraryDownloadedChaptersStateFamily._();
|
||||
|
||||
final class LibraryDownloadedChaptersStateProvider
|
||||
extends $NotifierProvider<LibraryDownloadedChaptersState, bool> {
|
||||
const LibraryDownloadedChaptersStateProvider._({
|
||||
LibraryDownloadedChaptersStateProvider._({
|
||||
required LibraryDownloadedChaptersStateFamily super.from,
|
||||
required ({ItemType itemType, Settings settings}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -973,7 +986,7 @@ final class LibraryDownloadedChaptersStateFamily extends $Family
|
|||
bool,
|
||||
({ItemType itemType, Settings settings})
|
||||
> {
|
||||
const LibraryDownloadedChaptersStateFamily._()
|
||||
LibraryDownloadedChaptersStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'libraryDownloadedChaptersStateProvider',
|
||||
|
|
@ -1003,7 +1016,6 @@ abstract class _$LibraryDownloadedChaptersState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args.itemType, settings: _$args.settings);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -1013,16 +1025,19 @@ abstract class _$LibraryDownloadedChaptersState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(itemType: _$args.itemType, settings: _$args.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(LibraryLanguageState)
|
||||
const libraryLanguageStateProvider = LibraryLanguageStateFamily._();
|
||||
final libraryLanguageStateProvider = LibraryLanguageStateFamily._();
|
||||
|
||||
final class LibraryLanguageStateProvider
|
||||
extends $NotifierProvider<LibraryLanguageState, bool> {
|
||||
const LibraryLanguageStateProvider._({
|
||||
LibraryLanguageStateProvider._({
|
||||
required LibraryLanguageStateFamily super.from,
|
||||
required ({ItemType itemType, Settings settings}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -1078,7 +1093,7 @@ final class LibraryLanguageStateFamily extends $Family
|
|||
bool,
|
||||
({ItemType itemType, Settings settings})
|
||||
> {
|
||||
const LibraryLanguageStateFamily._()
|
||||
LibraryLanguageStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'libraryLanguageStateProvider',
|
||||
|
|
@ -1108,7 +1123,6 @@ abstract class _$LibraryLanguageState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args.itemType, settings: _$args.settings);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -1118,16 +1132,19 @@ abstract class _$LibraryLanguageState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(itemType: _$args.itemType, settings: _$args.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(LibraryLocalSourceState)
|
||||
const libraryLocalSourceStateProvider = LibraryLocalSourceStateFamily._();
|
||||
final libraryLocalSourceStateProvider = LibraryLocalSourceStateFamily._();
|
||||
|
||||
final class LibraryLocalSourceStateProvider
|
||||
extends $NotifierProvider<LibraryLocalSourceState, bool> {
|
||||
const LibraryLocalSourceStateProvider._({
|
||||
LibraryLocalSourceStateProvider._({
|
||||
required LibraryLocalSourceStateFamily super.from,
|
||||
required ({ItemType itemType, Settings settings}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -1184,7 +1201,7 @@ final class LibraryLocalSourceStateFamily extends $Family
|
|||
bool,
|
||||
({ItemType itemType, Settings settings})
|
||||
> {
|
||||
const LibraryLocalSourceStateFamily._()
|
||||
LibraryLocalSourceStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'libraryLocalSourceStateProvider',
|
||||
|
|
@ -1214,7 +1231,6 @@ abstract class _$LibraryLocalSourceState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args.itemType, settings: _$args.settings);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -1224,17 +1240,20 @@ abstract class _$LibraryLocalSourceState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(itemType: _$args.itemType, settings: _$args.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(LibraryShowNumbersOfItemsState)
|
||||
const libraryShowNumbersOfItemsStateProvider =
|
||||
final libraryShowNumbersOfItemsStateProvider =
|
||||
LibraryShowNumbersOfItemsStateFamily._();
|
||||
|
||||
final class LibraryShowNumbersOfItemsStateProvider
|
||||
extends $NotifierProvider<LibraryShowNumbersOfItemsState, bool> {
|
||||
const LibraryShowNumbersOfItemsStateProvider._({
|
||||
LibraryShowNumbersOfItemsStateProvider._({
|
||||
required LibraryShowNumbersOfItemsStateFamily super.from,
|
||||
required ({ItemType itemType, Settings settings}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -1291,7 +1310,7 @@ final class LibraryShowNumbersOfItemsStateFamily extends $Family
|
|||
bool,
|
||||
({ItemType itemType, Settings settings})
|
||||
> {
|
||||
const LibraryShowNumbersOfItemsStateFamily._()
|
||||
LibraryShowNumbersOfItemsStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'libraryShowNumbersOfItemsStateProvider',
|
||||
|
|
@ -1321,7 +1340,6 @@ abstract class _$LibraryShowNumbersOfItemsState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args.itemType, settings: _$args.settings);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -1331,17 +1349,20 @@ abstract class _$LibraryShowNumbersOfItemsState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(itemType: _$args.itemType, settings: _$args.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(LibraryShowContinueReadingButtonState)
|
||||
const libraryShowContinueReadingButtonStateProvider =
|
||||
final libraryShowContinueReadingButtonStateProvider =
|
||||
LibraryShowContinueReadingButtonStateFamily._();
|
||||
|
||||
final class LibraryShowContinueReadingButtonStateProvider
|
||||
extends $NotifierProvider<LibraryShowContinueReadingButtonState, bool> {
|
||||
const LibraryShowContinueReadingButtonStateProvider._({
|
||||
LibraryShowContinueReadingButtonStateProvider._({
|
||||
required LibraryShowContinueReadingButtonStateFamily super.from,
|
||||
required ({ItemType itemType, Settings settings}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -1400,7 +1421,7 @@ final class LibraryShowContinueReadingButtonStateFamily extends $Family
|
|||
bool,
|
||||
({ItemType itemType, Settings settings})
|
||||
> {
|
||||
const LibraryShowContinueReadingButtonStateFamily._()
|
||||
LibraryShowContinueReadingButtonStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'libraryShowContinueReadingButtonStateProvider',
|
||||
|
|
@ -1430,7 +1451,6 @@ abstract class _$LibraryShowContinueReadingButtonState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args.itemType, settings: _$args.settings);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -1440,16 +1460,19 @@ abstract class _$LibraryShowContinueReadingButtonState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(itemType: _$args.itemType, settings: _$args.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(SortLibraryMangaState)
|
||||
const sortLibraryMangaStateProvider = SortLibraryMangaStateFamily._();
|
||||
final sortLibraryMangaStateProvider = SortLibraryMangaStateFamily._();
|
||||
|
||||
final class SortLibraryMangaStateProvider
|
||||
extends $NotifierProvider<SortLibraryMangaState, SortLibraryManga> {
|
||||
const SortLibraryMangaStateProvider._({
|
||||
SortLibraryMangaStateProvider._({
|
||||
required SortLibraryMangaStateFamily super.from,
|
||||
required ({ItemType itemType, Settings settings}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -1505,7 +1528,7 @@ final class SortLibraryMangaStateFamily extends $Family
|
|||
SortLibraryManga,
|
||||
({ItemType itemType, Settings settings})
|
||||
> {
|
||||
const SortLibraryMangaStateFamily._()
|
||||
SortLibraryMangaStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'sortLibraryMangaStateProvider',
|
||||
|
|
@ -1538,7 +1561,6 @@ abstract class _$SortLibraryMangaState extends $Notifier<SortLibraryManga> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(itemType: _$args.itemType, settings: _$args.settings);
|
||||
final ref = this.ref as $Ref<SortLibraryManga, SortLibraryManga>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -1548,16 +1570,19 @@ abstract class _$SortLibraryMangaState extends $Notifier<SortLibraryManga> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(itemType: _$args.itemType, settings: _$args.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(MangasListState)
|
||||
const mangasListStateProvider = MangasListStateProvider._();
|
||||
final mangasListStateProvider = MangasListStateProvider._();
|
||||
|
||||
final class MangasListStateProvider
|
||||
extends $NotifierProvider<MangasListState, List<int>> {
|
||||
const MangasListStateProvider._()
|
||||
MangasListStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -1591,7 +1616,6 @@ abstract class _$MangasListState extends $Notifier<List<int>> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<List<int>, List<int>>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -1601,16 +1625,16 @@ abstract class _$MangasListState extends $Notifier<List<int>> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(MangasSetIsReadState)
|
||||
const mangasSetIsReadStateProvider = MangasSetIsReadStateFamily._();
|
||||
final mangasSetIsReadStateProvider = MangasSetIsReadStateFamily._();
|
||||
|
||||
final class MangasSetIsReadStateProvider
|
||||
extends $NotifierProvider<MangasSetIsReadState, void> {
|
||||
const MangasSetIsReadStateProvider._({
|
||||
MangasSetIsReadStateProvider._({
|
||||
required MangasSetIsReadStateFamily super.from,
|
||||
required ({List<int> mangaIds, bool markAsRead}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -1666,7 +1690,7 @@ final class MangasSetIsReadStateFamily extends $Family
|
|||
void,
|
||||
({List<int> mangaIds, bool markAsRead})
|
||||
> {
|
||||
const MangasSetIsReadStateFamily._()
|
||||
MangasSetIsReadStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'mangasSetIsReadStateProvider',
|
||||
|
|
@ -1696,7 +1720,6 @@ abstract class _$MangasSetIsReadState extends $Notifier<void> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build(mangaIds: _$args.mangaIds, markAsRead: _$args.markAsRead);
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -1706,6 +1729,9 @@ abstract class _$MangasSetIsReadState extends $Notifier<void> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(mangaIds: _$args.mangaIds, markAsRead: _$args.markAsRead),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,18 +85,14 @@ Future importArchivesFromFile(
|
|||
: Uint8List.fromList(coverImage).getCoverImage,
|
||||
);
|
||||
}
|
||||
for (var chapter in book.Chapters ?? []) {
|
||||
chapters.add(
|
||||
Chapter(
|
||||
mangaId: mangaId,
|
||||
name: chapter.Title is String && chapter.Title.isEmpty
|
||||
? "Book"
|
||||
: chapter.Title,
|
||||
archivePath: file.path,
|
||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||
)..manga.value = manga,
|
||||
);
|
||||
}
|
||||
chapters.add(
|
||||
Chapter(
|
||||
mangaId: mangaId,
|
||||
name: book.Title,
|
||||
archivePath: file.path,
|
||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||
)..manga.value = manga,
|
||||
);
|
||||
} else {
|
||||
chapters.add(
|
||||
Chapter(
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ part of 'local_archive.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(importArchivesFromFile)
|
||||
const importArchivesFromFileProvider = ImportArchivesFromFileFamily._();
|
||||
final importArchivesFromFileProvider = ImportArchivesFromFileFamily._();
|
||||
|
||||
final class ImportArchivesFromFileProvider
|
||||
extends $FunctionalProvider<AsyncValue<dynamic>, dynamic, FutureOr<dynamic>>
|
||||
with $FutureModifier<dynamic>, $FutureProvider<dynamic> {
|
||||
const ImportArchivesFromFileProvider._({
|
||||
ImportArchivesFromFileProvider._({
|
||||
required ImportArchivesFromFileFamily super.from,
|
||||
required (Manga?, {ItemType itemType, bool init}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -65,7 +65,7 @@ final class ImportArchivesFromFileProvider
|
|||
}
|
||||
|
||||
String _$importArchivesFromFileHash() =>
|
||||
r'bfc12f37ab08a8f72a6fcff8d72afcff49ba5cc8';
|
||||
r'8a26aaed0c29e76899ab37c6f64a1f8b80792e41';
|
||||
|
||||
final class ImportArchivesFromFileFamily extends $Family
|
||||
with
|
||||
|
|
@ -73,7 +73,7 @@ final class ImportArchivesFromFileFamily extends $Family
|
|||
FutureOr<dynamic>,
|
||||
(Manga?, {ItemType itemType, bool init})
|
||||
> {
|
||||
const ImportArchivesFromFileFamily._()
|
||||
ImportArchivesFromFileFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'importArchivesFromFileProvider',
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ part of 'migration.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(migration)
|
||||
const migrationProvider = MigrationProvider._();
|
||||
final migrationProvider = MigrationProvider._();
|
||||
|
||||
final class MigrationProvider
|
||||
extends $FunctionalProvider<AsyncValue<void>, void, FutureOr<void>>
|
||||
with $FutureModifier<void>, $FutureProvider<void> {
|
||||
const MigrationProvider._()
|
||||
MigrationProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'archive_reader_providers.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(getArchivesDataFromDirectory)
|
||||
const getArchivesDataFromDirectoryProvider =
|
||||
final getArchivesDataFromDirectoryProvider =
|
||||
GetArchivesDataFromDirectoryFamily._();
|
||||
|
||||
final class GetArchivesDataFromDirectoryProvider
|
||||
|
|
@ -23,7 +23,7 @@ final class GetArchivesDataFromDirectoryProvider
|
|||
with
|
||||
$FutureModifier<List<(String, LocalExtensionType, Uint8List, String)>>,
|
||||
$FutureProvider<List<(String, LocalExtensionType, Uint8List, String)>> {
|
||||
const GetArchivesDataFromDirectoryProvider._({
|
||||
GetArchivesDataFromDirectoryProvider._({
|
||||
required GetArchivesDataFromDirectoryFamily super.from,
|
||||
required String super.argument,
|
||||
}) : super(
|
||||
|
|
@ -78,7 +78,7 @@ final class GetArchivesDataFromDirectoryFamily extends $Family
|
|||
FutureOr<List<(String, LocalExtensionType, Uint8List, String)>>,
|
||||
String
|
||||
> {
|
||||
const GetArchivesDataFromDirectoryFamily._()
|
||||
GetArchivesDataFromDirectoryFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getArchivesDataFromDirectoryProvider',
|
||||
|
|
@ -95,7 +95,7 @@ final class GetArchivesDataFromDirectoryFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(getArchiveDataFromDirectory)
|
||||
const getArchiveDataFromDirectoryProvider =
|
||||
final getArchiveDataFromDirectoryProvider =
|
||||
GetArchiveDataFromDirectoryFamily._();
|
||||
|
||||
final class GetArchiveDataFromDirectoryProvider
|
||||
|
|
@ -108,7 +108,7 @@ final class GetArchiveDataFromDirectoryProvider
|
|||
with
|
||||
$FutureModifier<List<LocalArchive>>,
|
||||
$FutureProvider<List<LocalArchive>> {
|
||||
const GetArchiveDataFromDirectoryProvider._({
|
||||
GetArchiveDataFromDirectoryProvider._({
|
||||
required GetArchiveDataFromDirectoryFamily super.from,
|
||||
required String super.argument,
|
||||
}) : super(
|
||||
|
|
@ -158,7 +158,7 @@ String _$getArchiveDataFromDirectoryHash() =>
|
|||
|
||||
final class GetArchiveDataFromDirectoryFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<List<LocalArchive>>, String> {
|
||||
const GetArchiveDataFromDirectoryFamily._()
|
||||
GetArchiveDataFromDirectoryFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getArchiveDataFromDirectoryProvider',
|
||||
|
|
@ -175,7 +175,7 @@ final class GetArchiveDataFromDirectoryFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(getArchivesDataFromFile)
|
||||
const getArchivesDataFromFileProvider = GetArchivesDataFromFileFamily._();
|
||||
final getArchivesDataFromFileProvider = GetArchivesDataFromFileFamily._();
|
||||
|
||||
final class GetArchivesDataFromFileProvider
|
||||
extends
|
||||
|
|
@ -187,7 +187,7 @@ final class GetArchivesDataFromFileProvider
|
|||
with
|
||||
$FutureModifier<(String, LocalExtensionType, Uint8List, String)>,
|
||||
$FutureProvider<(String, LocalExtensionType, Uint8List, String)> {
|
||||
const GetArchivesDataFromFileProvider._({
|
||||
GetArchivesDataFromFileProvider._({
|
||||
required GetArchivesDataFromFileFamily super.from,
|
||||
required String super.argument,
|
||||
}) : super(
|
||||
|
|
@ -240,7 +240,7 @@ final class GetArchivesDataFromFileFamily extends $Family
|
|||
FutureOr<(String, LocalExtensionType, Uint8List, String)>,
|
||||
String
|
||||
> {
|
||||
const GetArchivesDataFromFileFamily._()
|
||||
GetArchivesDataFromFileFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getArchivesDataFromFileProvider',
|
||||
|
|
@ -257,7 +257,7 @@ final class GetArchivesDataFromFileFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(getArchiveDataFromFile)
|
||||
const getArchiveDataFromFileProvider = GetArchiveDataFromFileFamily._();
|
||||
final getArchiveDataFromFileProvider = GetArchiveDataFromFileFamily._();
|
||||
|
||||
final class GetArchiveDataFromFileProvider
|
||||
extends
|
||||
|
|
@ -267,7 +267,7 @@ final class GetArchiveDataFromFileProvider
|
|||
FutureOr<LocalArchive>
|
||||
>
|
||||
with $FutureModifier<LocalArchive>, $FutureProvider<LocalArchive> {
|
||||
const GetArchiveDataFromFileProvider._({
|
||||
GetArchiveDataFromFileProvider._({
|
||||
required GetArchiveDataFromFileFamily super.from,
|
||||
required String super.argument,
|
||||
}) : super(
|
||||
|
|
@ -317,7 +317,7 @@ String _$getArchiveDataFromFileHash() =>
|
|||
|
||||
final class GetArchiveDataFromFileFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<LocalArchive>, String> {
|
||||
const GetArchiveDataFromFileFamily._()
|
||||
GetArchiveDataFromFileFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getArchiveDataFromFileProvider',
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ part of 'isar_providers.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(getMangaDetailStream)
|
||||
const getMangaDetailStreamProvider = GetMangaDetailStreamFamily._();
|
||||
final getMangaDetailStreamProvider = GetMangaDetailStreamFamily._();
|
||||
|
||||
final class GetMangaDetailStreamProvider
|
||||
extends $FunctionalProvider<AsyncValue<Manga?>, Manga?, Stream<Manga?>>
|
||||
with $FutureModifier<Manga?>, $StreamProvider<Manga?> {
|
||||
const GetMangaDetailStreamProvider._({
|
||||
GetMangaDetailStreamProvider._({
|
||||
required GetMangaDetailStreamFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
|
|
@ -63,7 +63,7 @@ String _$getMangaDetailStreamHash() =>
|
|||
|
||||
final class GetMangaDetailStreamFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<Manga?>, int> {
|
||||
const GetMangaDetailStreamFamily._()
|
||||
GetMangaDetailStreamFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getMangaDetailStreamProvider',
|
||||
|
|
@ -80,7 +80,7 @@ final class GetMangaDetailStreamFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(getChaptersStream)
|
||||
const getChaptersStreamProvider = GetChaptersStreamFamily._();
|
||||
final getChaptersStreamProvider = GetChaptersStreamFamily._();
|
||||
|
||||
final class GetChaptersStreamProvider
|
||||
extends
|
||||
|
|
@ -90,7 +90,7 @@ final class GetChaptersStreamProvider
|
|||
Stream<List<Chapter>>
|
||||
>
|
||||
with $FutureModifier<List<Chapter>>, $StreamProvider<List<Chapter>> {
|
||||
const GetChaptersStreamProvider._({
|
||||
GetChaptersStreamProvider._({
|
||||
required GetChaptersStreamFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
|
|
@ -138,7 +138,7 @@ String _$getChaptersStreamHash() => r'0f03db54c5a639c4356a81e4bad50fa8a077ceac';
|
|||
|
||||
final class GetChaptersStreamFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<List<Chapter>>, int> {
|
||||
const GetChaptersStreamFamily._()
|
||||
GetChaptersStreamFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getChaptersStreamProvider',
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ part of 'state_providers.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ChaptersListState)
|
||||
const chaptersListStateProvider = ChaptersListStateProvider._();
|
||||
final chaptersListStateProvider = ChaptersListStateProvider._();
|
||||
|
||||
final class ChaptersListStateProvider
|
||||
extends $NotifierProvider<ChaptersListState, List<Chapter>> {
|
||||
const ChaptersListStateProvider._()
|
||||
ChaptersListStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -48,7 +48,6 @@ abstract class _$ChaptersListState extends $Notifier<List<Chapter>> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<List<Chapter>, List<Chapter>>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -58,16 +57,16 @@ abstract class _$ChaptersListState extends $Notifier<List<Chapter>> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(IsLongPressedState)
|
||||
const isLongPressedStateProvider = IsLongPressedStateProvider._();
|
||||
final isLongPressedStateProvider = IsLongPressedStateProvider._();
|
||||
|
||||
final class IsLongPressedStateProvider
|
||||
extends $NotifierProvider<IsLongPressedState, bool> {
|
||||
const IsLongPressedStateProvider._()
|
||||
IsLongPressedStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -102,7 +101,6 @@ abstract class _$IsLongPressedState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -112,16 +110,16 @@ abstract class _$IsLongPressedState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(IsExtendedState)
|
||||
const isExtendedStateProvider = IsExtendedStateProvider._();
|
||||
final isExtendedStateProvider = IsExtendedStateProvider._();
|
||||
|
||||
final class IsExtendedStateProvider
|
||||
extends $NotifierProvider<IsExtendedState, bool> {
|
||||
const IsExtendedStateProvider._()
|
||||
IsExtendedStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -155,7 +153,6 @@ abstract class _$IsExtendedState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -165,16 +162,16 @@ abstract class _$IsExtendedState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(SortChapterState)
|
||||
const sortChapterStateProvider = SortChapterStateFamily._();
|
||||
final sortChapterStateProvider = SortChapterStateFamily._();
|
||||
|
||||
final class SortChapterStateProvider
|
||||
extends $NotifierProvider<SortChapterState, SortChapter> {
|
||||
const SortChapterStateProvider._({
|
||||
SortChapterStateProvider._({
|
||||
required SortChapterStateFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
|
|
@ -229,7 +226,7 @@ final class SortChapterStateFamily extends $Family
|
|||
SortChapter,
|
||||
int
|
||||
> {
|
||||
const SortChapterStateFamily._()
|
||||
SortChapterStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'sortChapterStateProvider',
|
||||
|
|
@ -253,7 +250,6 @@ abstract class _$SortChapterState extends $Notifier<SortChapter> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(mangaId: _$args);
|
||||
final ref = this.ref as $Ref<SortChapter, SortChapter>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -263,17 +259,17 @@ abstract class _$SortChapterState extends $Notifier<SortChapter> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(mangaId: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ChapterFilterDownloadedState)
|
||||
const chapterFilterDownloadedStateProvider =
|
||||
final chapterFilterDownloadedStateProvider =
|
||||
ChapterFilterDownloadedStateFamily._();
|
||||
|
||||
final class ChapterFilterDownloadedStateProvider
|
||||
extends $NotifierProvider<ChapterFilterDownloadedState, int> {
|
||||
const ChapterFilterDownloadedStateProvider._({
|
||||
ChapterFilterDownloadedStateProvider._({
|
||||
required ChapterFilterDownloadedStateFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
|
|
@ -324,7 +320,7 @@ String _$chapterFilterDownloadedStateHash() =>
|
|||
final class ChapterFilterDownloadedStateFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<ChapterFilterDownloadedState, int, int, int, int> {
|
||||
const ChapterFilterDownloadedStateFamily._()
|
||||
ChapterFilterDownloadedStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'chapterFilterDownloadedStateProvider',
|
||||
|
|
@ -348,7 +344,6 @@ abstract class _$ChapterFilterDownloadedState extends $Notifier<int> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(mangaId: _$args);
|
||||
final ref = this.ref as $Ref<int, int>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -358,16 +353,16 @@ abstract class _$ChapterFilterDownloadedState extends $Notifier<int> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(mangaId: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ChapterFilterUnreadState)
|
||||
const chapterFilterUnreadStateProvider = ChapterFilterUnreadStateFamily._();
|
||||
final chapterFilterUnreadStateProvider = ChapterFilterUnreadStateFamily._();
|
||||
|
||||
final class ChapterFilterUnreadStateProvider
|
||||
extends $NotifierProvider<ChapterFilterUnreadState, int> {
|
||||
const ChapterFilterUnreadStateProvider._({
|
||||
ChapterFilterUnreadStateProvider._({
|
||||
required ChapterFilterUnreadStateFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
|
|
@ -417,7 +412,7 @@ String _$chapterFilterUnreadStateHash() =>
|
|||
|
||||
final class ChapterFilterUnreadStateFamily extends $Family
|
||||
with $ClassFamilyOverride<ChapterFilterUnreadState, int, int, int, int> {
|
||||
const ChapterFilterUnreadStateFamily._()
|
||||
ChapterFilterUnreadStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'chapterFilterUnreadStateProvider',
|
||||
|
|
@ -441,7 +436,6 @@ abstract class _$ChapterFilterUnreadState extends $Notifier<int> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(mangaId: _$args);
|
||||
final ref = this.ref as $Ref<int, int>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -451,17 +445,17 @@ abstract class _$ChapterFilterUnreadState extends $Notifier<int> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(mangaId: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ChapterFilterBookmarkedState)
|
||||
const chapterFilterBookmarkedStateProvider =
|
||||
final chapterFilterBookmarkedStateProvider =
|
||||
ChapterFilterBookmarkedStateFamily._();
|
||||
|
||||
final class ChapterFilterBookmarkedStateProvider
|
||||
extends $NotifierProvider<ChapterFilterBookmarkedState, int> {
|
||||
const ChapterFilterBookmarkedStateProvider._({
|
||||
ChapterFilterBookmarkedStateProvider._({
|
||||
required ChapterFilterBookmarkedStateFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
|
|
@ -512,7 +506,7 @@ String _$chapterFilterBookmarkedStateHash() =>
|
|||
final class ChapterFilterBookmarkedStateFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<ChapterFilterBookmarkedState, int, int, int, int> {
|
||||
const ChapterFilterBookmarkedStateFamily._()
|
||||
ChapterFilterBookmarkedStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'chapterFilterBookmarkedStateProvider',
|
||||
|
|
@ -536,7 +530,6 @@ abstract class _$ChapterFilterBookmarkedState extends $Notifier<int> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(mangaId: _$args);
|
||||
final ref = this.ref as $Ref<int, int>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -546,16 +539,16 @@ abstract class _$ChapterFilterBookmarkedState extends $Notifier<int> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(mangaId: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ChapterFilterResultState)
|
||||
const chapterFilterResultStateProvider = ChapterFilterResultStateFamily._();
|
||||
final chapterFilterResultStateProvider = ChapterFilterResultStateFamily._();
|
||||
|
||||
final class ChapterFilterResultStateProvider
|
||||
extends $NotifierProvider<ChapterFilterResultState, bool> {
|
||||
const ChapterFilterResultStateProvider._({
|
||||
ChapterFilterResultStateProvider._({
|
||||
required ChapterFilterResultStateFamily super.from,
|
||||
required Manga super.argument,
|
||||
}) : super(
|
||||
|
|
@ -612,7 +605,7 @@ final class ChapterFilterResultStateFamily extends $Family
|
|||
bool,
|
||||
Manga
|
||||
> {
|
||||
const ChapterFilterResultStateFamily._()
|
||||
ChapterFilterResultStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'chapterFilterResultStateProvider',
|
||||
|
|
@ -636,7 +629,6 @@ abstract class _$ChapterFilterResultState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(manga: _$args);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -646,16 +638,16 @@ abstract class _$ChapterFilterResultState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(manga: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ChapterSetIsBookmarkState)
|
||||
const chapterSetIsBookmarkStateProvider = ChapterSetIsBookmarkStateFamily._();
|
||||
final chapterSetIsBookmarkStateProvider = ChapterSetIsBookmarkStateFamily._();
|
||||
|
||||
final class ChapterSetIsBookmarkStateProvider
|
||||
extends $NotifierProvider<ChapterSetIsBookmarkState, void> {
|
||||
const ChapterSetIsBookmarkStateProvider._({
|
||||
ChapterSetIsBookmarkStateProvider._({
|
||||
required ChapterSetIsBookmarkStateFamily super.from,
|
||||
required Manga super.argument,
|
||||
}) : super(
|
||||
|
|
@ -712,7 +704,7 @@ final class ChapterSetIsBookmarkStateFamily extends $Family
|
|||
void,
|
||||
Manga
|
||||
> {
|
||||
const ChapterSetIsBookmarkStateFamily._()
|
||||
ChapterSetIsBookmarkStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'chapterSetIsBookmarkStateProvider',
|
||||
|
|
@ -736,7 +728,6 @@ abstract class _$ChapterSetIsBookmarkState extends $Notifier<void> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build(manga: _$args);
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -746,16 +737,16 @@ abstract class _$ChapterSetIsBookmarkState extends $Notifier<void> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
element.handleCreate(ref, () => build(manga: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ChapterSetIsReadState)
|
||||
const chapterSetIsReadStateProvider = ChapterSetIsReadStateFamily._();
|
||||
final chapterSetIsReadStateProvider = ChapterSetIsReadStateFamily._();
|
||||
|
||||
final class ChapterSetIsReadStateProvider
|
||||
extends $NotifierProvider<ChapterSetIsReadState, void> {
|
||||
const ChapterSetIsReadStateProvider._({
|
||||
ChapterSetIsReadStateProvider._({
|
||||
required ChapterSetIsReadStateFamily super.from,
|
||||
required Manga super.argument,
|
||||
}) : super(
|
||||
|
|
@ -804,7 +795,7 @@ String _$chapterSetIsReadStateHash() =>
|
|||
|
||||
final class ChapterSetIsReadStateFamily extends $Family
|
||||
with $ClassFamilyOverride<ChapterSetIsReadState, void, void, void, Manga> {
|
||||
const ChapterSetIsReadStateFamily._()
|
||||
ChapterSetIsReadStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'chapterSetIsReadStateProvider',
|
||||
|
|
@ -828,7 +819,6 @@ abstract class _$ChapterSetIsReadState extends $Notifier<void> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build(manga: _$args);
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -838,16 +828,16 @@ abstract class _$ChapterSetIsReadState extends $Notifier<void> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
element.handleCreate(ref, () => build(manga: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ChapterSetDownloadState)
|
||||
const chapterSetDownloadStateProvider = ChapterSetDownloadStateFamily._();
|
||||
final chapterSetDownloadStateProvider = ChapterSetDownloadStateFamily._();
|
||||
|
||||
final class ChapterSetDownloadStateProvider
|
||||
extends $NotifierProvider<ChapterSetDownloadState, void> {
|
||||
const ChapterSetDownloadStateProvider._({
|
||||
ChapterSetDownloadStateProvider._({
|
||||
required ChapterSetDownloadStateFamily super.from,
|
||||
required Manga super.argument,
|
||||
}) : super(
|
||||
|
|
@ -898,7 +888,7 @@ String _$chapterSetDownloadStateHash() =>
|
|||
final class ChapterSetDownloadStateFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<ChapterSetDownloadState, void, void, void, Manga> {
|
||||
const ChapterSetDownloadStateFamily._()
|
||||
ChapterSetDownloadStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'chapterSetDownloadStateProvider',
|
||||
|
|
@ -922,7 +912,6 @@ abstract class _$ChapterSetDownloadState extends $Notifier<void> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build(manga: _$args);
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -932,16 +921,16 @@ abstract class _$ChapterSetDownloadState extends $Notifier<void> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
element.handleCreate(ref, () => build(manga: _$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ChaptersListttState)
|
||||
const chaptersListttStateProvider = ChaptersListttStateProvider._();
|
||||
final chaptersListttStateProvider = ChaptersListttStateProvider._();
|
||||
|
||||
final class ChaptersListttStateProvider
|
||||
extends $NotifierProvider<ChaptersListttState, List<Chapter>> {
|
||||
const ChaptersListttStateProvider._()
|
||||
ChaptersListttStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -976,7 +965,6 @@ abstract class _$ChaptersListttState extends $Notifier<List<Chapter>> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<List<Chapter>, List<Chapter>>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -986,12 +974,12 @@ abstract class _$ChaptersListttState extends $Notifier<List<Chapter>> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ScanlatorsFilterState)
|
||||
const scanlatorsFilterStateProvider = ScanlatorsFilterStateFamily._();
|
||||
final scanlatorsFilterStateProvider = ScanlatorsFilterStateFamily._();
|
||||
|
||||
final class ScanlatorsFilterStateProvider
|
||||
extends
|
||||
|
|
@ -999,7 +987,7 @@ final class ScanlatorsFilterStateProvider
|
|||
ScanlatorsFilterState,
|
||||
(List<String>, List<String>, List<String>)
|
||||
> {
|
||||
const ScanlatorsFilterStateProvider._({
|
||||
ScanlatorsFilterStateProvider._({
|
||||
required ScanlatorsFilterStateFamily super.from,
|
||||
required Manga super.argument,
|
||||
}) : super(
|
||||
|
|
@ -1056,7 +1044,7 @@ final class ScanlatorsFilterStateFamily extends $Family
|
|||
(List<String>, List<String>, List<String>),
|
||||
Manga
|
||||
> {
|
||||
const ScanlatorsFilterStateFamily._()
|
||||
ScanlatorsFilterStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'scanlatorsFilterStateProvider',
|
||||
|
|
@ -1081,7 +1069,6 @@ abstract class _$ScanlatorsFilterState
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(_$args);
|
||||
final ref =
|
||||
this.ref
|
||||
as $Ref<
|
||||
|
|
@ -1099,6 +1086,6 @@ abstract class _$ScanlatorsFilterState
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(_$args));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ part of 'track_state_providers.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(TrackState)
|
||||
const trackStateProvider = TrackStateFamily._();
|
||||
final trackStateProvider = TrackStateFamily._();
|
||||
|
||||
final class TrackStateProvider extends $NotifierProvider<TrackState, Track> {
|
||||
const TrackStateProvider._({
|
||||
TrackStateProvider._({
|
||||
required TrackStateFamily super.from,
|
||||
required ({Track? track, ItemType? itemType, dynamic widgetRef})
|
||||
super.argument,
|
||||
|
|
@ -69,7 +69,7 @@ final class TrackStateFamily extends $Family
|
|||
Track,
|
||||
({Track? track, ItemType? itemType, dynamic widgetRef})
|
||||
> {
|
||||
const TrackStateFamily._()
|
||||
TrackStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'trackStateProvider',
|
||||
|
|
@ -106,11 +106,6 @@ abstract class _$TrackState extends $Notifier<Track> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(
|
||||
track: _$args.track,
|
||||
itemType: _$args.itemType,
|
||||
widgetRef: _$args.widgetRef,
|
||||
);
|
||||
final ref = this.ref as $Ref<Track, Track>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -120,17 +115,24 @@ abstract class _$TrackState extends $Notifier<Track> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(
|
||||
track: _$args.track,
|
||||
itemType: _$args.itemType,
|
||||
widgetRef: _$args.widgetRef,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(LastTrackerLibraryLocationState)
|
||||
const lastTrackerLibraryLocationStateProvider =
|
||||
final lastTrackerLibraryLocationStateProvider =
|
||||
LastTrackerLibraryLocationStateProvider._();
|
||||
|
||||
final class LastTrackerLibraryLocationStateProvider
|
||||
extends $NotifierProvider<LastTrackerLibraryLocationState, (int, bool)> {
|
||||
const LastTrackerLibraryLocationStateProvider._()
|
||||
LastTrackerLibraryLocationStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -166,7 +168,6 @@ abstract class _$LastTrackerLibraryLocationState
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<(int, bool), (int, bool)>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -176,6 +177,6 @@ abstract class _$LastTrackerLibraryLocationState
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Future<dynamic> updateMangaDetail(
|
|||
|
||||
final genre =
|
||||
getManga.genre
|
||||
?.map((e) => e.toString().trim().trimLeft().trimRight())
|
||||
?.map((e) => e.toString().trim())
|
||||
.toList()
|
||||
.toSet()
|
||||
.toList() ??
|
||||
|
|
@ -80,7 +80,7 @@ Future<dynamic> updateMangaDetail(
|
|||
for (var i = 0; i < newChapsIndex; i++) {
|
||||
final chapter = Chapter(
|
||||
name: chaps[i].name!,
|
||||
url: chaps[i].url!.trim().trimLeft().trimRight(),
|
||||
url: chaps[i].url!.trim(),
|
||||
dateUpload: chaps[i].dateUpload == null
|
||||
? DateTime.now().millisecondsSinceEpoch.toString()
|
||||
: chaps[i].dateUpload.toString(),
|
||||
|
|
@ -171,8 +171,8 @@ Future<dynamic> updateMangaDetail(
|
|||
|
||||
extension DefaultValueExtension on String? {
|
||||
String? trimmedOrDefault(String? defaultValue) {
|
||||
if (this?.trim().trimLeft().trimRight().isNotEmpty ?? false) {
|
||||
return this!.trim().trimLeft().trimRight();
|
||||
if (this?.trim().isNotEmpty ?? false) {
|
||||
return this!.trim();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ part of 'update_manga_detail_providers.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(updateMangaDetail)
|
||||
const updateMangaDetailProvider = UpdateMangaDetailFamily._();
|
||||
final updateMangaDetailProvider = UpdateMangaDetailFamily._();
|
||||
|
||||
final class UpdateMangaDetailProvider
|
||||
extends $FunctionalProvider<AsyncValue<dynamic>, dynamic, FutureOr<dynamic>>
|
||||
with $FutureModifier<dynamic>, $FutureProvider<dynamic> {
|
||||
const UpdateMangaDetailProvider._({
|
||||
UpdateMangaDetailProvider._({
|
||||
required UpdateMangaDetailFamily super.from,
|
||||
required ({int? mangaId, bool isInit, bool showToast}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -64,7 +64,7 @@ final class UpdateMangaDetailProvider
|
|||
}
|
||||
}
|
||||
|
||||
String _$updateMangaDetailHash() => r'd056f9ff4213f437039edb67786c96dfda99dae0';
|
||||
String _$updateMangaDetailHash() => r'37da5f23f30126d15cedfaf42087f9ce11c3fc26';
|
||||
|
||||
final class UpdateMangaDetailFamily extends $Family
|
||||
with
|
||||
|
|
@ -72,7 +72,7 @@ final class UpdateMangaDetailFamily extends $Family
|
|||
FutureOr<dynamic>,
|
||||
({int? mangaId, bool isInit, bool showToast})
|
||||
> {
|
||||
const UpdateMangaDetailFamily._()
|
||||
UpdateMangaDetailFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'updateMangaDetailProvider',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'convert_to_cbz.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(convertToCBZ)
|
||||
const convertToCBZProvider = ConvertToCBZFamily._();
|
||||
final convertToCBZProvider = ConvertToCBZFamily._();
|
||||
|
||||
final class ConvertToCBZProvider
|
||||
extends
|
||||
|
|
@ -20,7 +20,7 @@ final class ConvertToCBZProvider
|
|||
FutureOr<List<String>>
|
||||
>
|
||||
with $FutureModifier<List<String>>, $FutureProvider<List<String>> {
|
||||
const ConvertToCBZProvider._({
|
||||
ConvertToCBZProvider._({
|
||||
required ConvertToCBZFamily super.from,
|
||||
required (String, String, String, List<String>) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -78,7 +78,7 @@ final class ConvertToCBZFamily extends $Family
|
|||
FutureOr<List<String>>,
|
||||
(String, String, String, List<String>)
|
||||
> {
|
||||
const ConvertToCBZFamily._()
|
||||
ConvertToCBZFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'convertToCBZProvider',
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ Future<void> downloadChapter(
|
|||
if (!file.existsSync()) {
|
||||
pages.add(
|
||||
PageUrl(
|
||||
page.url.trim().trimLeft().trimRight(),
|
||||
page.url.trim(),
|
||||
headers: pageHeaders,
|
||||
fileName: p.join(
|
||||
chapterDirectory.path,
|
||||
|
|
@ -325,7 +325,7 @@ Future<void> downloadChapter(
|
|||
if (!file.existsSync()) {
|
||||
pages.add(
|
||||
PageUrl(
|
||||
page.url.trim().trimLeft().trimRight(),
|
||||
page.url.trim(),
|
||||
headers: pageHeaders,
|
||||
fileName: p.join(mangaMainDirectory.path, "$chapterName.mp4"),
|
||||
),
|
||||
|
|
@ -406,6 +406,7 @@ Future<void> processDownloads(Ref ref, {bool? useWifi}) async {
|
|||
final downloadItem = ongoingDownloads[index++];
|
||||
final chapter = downloadItem.chapter.value!;
|
||||
chapter.cancelDownloads(downloadItem.id);
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
ref.read(
|
||||
downloadChapterProvider(
|
||||
chapter: chapter,
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ part of 'download_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(addDownloadToQueue)
|
||||
const addDownloadToQueueProvider = AddDownloadToQueueFamily._();
|
||||
final addDownloadToQueueProvider = AddDownloadToQueueFamily._();
|
||||
|
||||
final class AddDownloadToQueueProvider
|
||||
extends $FunctionalProvider<AsyncValue<void>, void, FutureOr<void>>
|
||||
with $FutureModifier<void>, $FutureProvider<void> {
|
||||
const AddDownloadToQueueProvider._({
|
||||
AddDownloadToQueueProvider._({
|
||||
required AddDownloadToQueueFamily super.from,
|
||||
required Chapter super.argument,
|
||||
}) : super(
|
||||
|
|
@ -63,7 +63,7 @@ String _$addDownloadToQueueHash() =>
|
|||
|
||||
final class AddDownloadToQueueFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<void>, Chapter> {
|
||||
const AddDownloadToQueueFamily._()
|
||||
AddDownloadToQueueFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'addDownloadToQueueProvider',
|
||||
|
|
@ -80,12 +80,12 @@ final class AddDownloadToQueueFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(downloadChapter)
|
||||
const downloadChapterProvider = DownloadChapterFamily._();
|
||||
final downloadChapterProvider = DownloadChapterFamily._();
|
||||
|
||||
final class DownloadChapterProvider
|
||||
extends $FunctionalProvider<AsyncValue<void>, void, FutureOr<void>>
|
||||
with $FutureModifier<void>, $FutureProvider<void> {
|
||||
const DownloadChapterProvider._({
|
||||
DownloadChapterProvider._({
|
||||
required DownloadChapterFamily super.from,
|
||||
required ({Chapter chapter, bool? useWifi, VoidCallback? callback})
|
||||
super.argument,
|
||||
|
|
@ -136,7 +136,7 @@ final class DownloadChapterProvider
|
|||
}
|
||||
}
|
||||
|
||||
String _$downloadChapterHash() => r'b64c5de46eafb0e7322eb599e49de3b09f027c04';
|
||||
String _$downloadChapterHash() => r'c503cef46aa7083316b023400f0aa470ae3a3bc4';
|
||||
|
||||
final class DownloadChapterFamily extends $Family
|
||||
with
|
||||
|
|
@ -144,7 +144,7 @@ final class DownloadChapterFamily extends $Family
|
|||
FutureOr<void>,
|
||||
({Chapter chapter, bool? useWifi, VoidCallback? callback})
|
||||
> {
|
||||
const DownloadChapterFamily._()
|
||||
DownloadChapterFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'downloadChapterProvider',
|
||||
|
|
@ -167,12 +167,12 @@ final class DownloadChapterFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(processDownloads)
|
||||
const processDownloadsProvider = ProcessDownloadsFamily._();
|
||||
final processDownloadsProvider = ProcessDownloadsFamily._();
|
||||
|
||||
final class ProcessDownloadsProvider
|
||||
extends $FunctionalProvider<AsyncValue<void>, void, FutureOr<void>>
|
||||
with $FutureModifier<void>, $FutureProvider<void> {
|
||||
const ProcessDownloadsProvider._({
|
||||
ProcessDownloadsProvider._({
|
||||
required ProcessDownloadsFamily super.from,
|
||||
required bool? super.argument,
|
||||
}) : super(
|
||||
|
|
@ -215,11 +215,11 @@ final class ProcessDownloadsProvider
|
|||
}
|
||||
}
|
||||
|
||||
String _$processDownloadsHash() => r'caebad3bb681d7b38de4d09325310fc08bc1cd0a';
|
||||
String _$processDownloadsHash() => r'36903a1ca0140ef7d55aa68ee34d8c74573e8e71';
|
||||
|
||||
final class ProcessDownloadsFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<void>, bool?> {
|
||||
const ProcessDownloadsFamily._()
|
||||
ProcessDownloadsFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'processDownloadsProvider',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import 'package:mangayomi/modules/manga/home/widget/mangas_card_selector.dart';
|
|||
import 'package:mangayomi/modules/widgets/gridview_widget.dart';
|
||||
import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
|
||||
import 'package:mangayomi/utils/global_style.dart';
|
||||
import 'package:mangayomi/utils/item_type_localization.dart';
|
||||
import 'package:marquee/marquee.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
|
|
@ -167,11 +168,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
Text(
|
||||
!isLocal
|
||||
? "${source.name}"
|
||||
: "${context.l10n.local_source} ${source.itemType == ItemType.manga
|
||||
? context.l10n.manga
|
||||
: source.itemType == ItemType.anime
|
||||
? context.l10n.anime
|
||||
: context.l10n.novel}",
|
||||
: "${context.l10n.local_source} ${source.itemType.localized(context.l10n)}",
|
||||
),
|
||||
source.notes != null && source.notes!.isNotEmpty
|
||||
? SizedBox(
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ part of 'state_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(MangaHomeDisplayTypeState)
|
||||
const mangaHomeDisplayTypeStateProvider = MangaHomeDisplayTypeStateProvider._();
|
||||
final mangaHomeDisplayTypeStateProvider = MangaHomeDisplayTypeStateProvider._();
|
||||
|
||||
final class MangaHomeDisplayTypeStateProvider
|
||||
extends $NotifierProvider<MangaHomeDisplayTypeState, DisplayType> {
|
||||
const MangaHomeDisplayTypeStateProvider._()
|
||||
MangaHomeDisplayTypeStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -49,7 +49,6 @@ abstract class _$MangaHomeDisplayTypeState extends $Notifier<DisplayType> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<DisplayType, DisplayType>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -59,6 +58,6 @@ abstract class _$MangaHomeDisplayTypeState extends $Notifier<DisplayType> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,309 +0,0 @@
|
|||
import 'package:extended_image/extended_image.dart';
|
||||
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/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';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
|
||||
class DoubleColummView extends StatefulWidget {
|
||||
final List<UChapDataPreload?> datas;
|
||||
final Function(UChapDataPreload datas) onLongPressData;
|
||||
final BackgroundColor backgroundColor;
|
||||
final Function(bool) isFailedToLoadImage;
|
||||
const DoubleColummView({
|
||||
super.key,
|
||||
required this.datas,
|
||||
required this.onLongPressData,
|
||||
required this.backgroundColor,
|
||||
required this.isFailedToLoadImage,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DoubleColummView> createState() => _DoubleColummViewState();
|
||||
}
|
||||
|
||||
class _DoubleColummViewState extends State<DoubleColummView>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _scaleAnimationController;
|
||||
late Animation<double> _animation;
|
||||
Alignment _scalePosition = Alignment.center;
|
||||
final PhotoViewController _photoViewController = PhotoViewController();
|
||||
final PhotoViewScaleStateController _photoViewScaleStateController =
|
||||
PhotoViewScaleStateController();
|
||||
Duration? _doubleTapAnimationDuration() {
|
||||
int doubleTapAnimationValue = isar.settings
|
||||
.getSync(227)!
|
||||
.doubleTapAnimationSpeed!;
|
||||
if (doubleTapAnimationValue == 0) {
|
||||
return const Duration(milliseconds: 10);
|
||||
} else if (doubleTapAnimationValue == 1) {
|
||||
return const Duration(milliseconds: 800);
|
||||
}
|
||||
return const Duration(milliseconds: 200);
|
||||
}
|
||||
|
||||
void _onScaleEnd(
|
||||
BuildContext context,
|
||||
ScaleEndDetails details,
|
||||
PhotoViewControllerValue controllerValue,
|
||||
) {
|
||||
if (controllerValue.scale! < 1) {
|
||||
_photoViewScaleStateController.reset();
|
||||
}
|
||||
}
|
||||
|
||||
double get pixelRatio => View.of(context).devicePixelRatio;
|
||||
Size get size => View.of(context).physicalSize / pixelRatio;
|
||||
Alignment _computeAlignmentByTapOffset(Offset offset) {
|
||||
return Alignment(
|
||||
(offset.dx - size.width / 2) / (size.width / 2),
|
||||
(offset.dy - size.height / 2) / (size.height / 2),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scaleAnimationController = AnimationController(
|
||||
duration: _doubleTapAnimationDuration(),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = Tween(begin: 1.0, end: 2.0).animate(
|
||||
CurvedAnimation(curve: Curves.ease, parent: _scaleAnimationController),
|
||||
);
|
||||
_animation.addListener(() {
|
||||
_photoViewController.scale = _animation.value;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scaleAnimationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _toggleScale(Offset tapPosition) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (_scaleAnimationController.isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_photoViewController.scale == 1.0) {
|
||||
_scalePosition = _computeAlignmentByTapOffset(tapPosition);
|
||||
|
||||
if (_scaleAnimationController.isCompleted) {
|
||||
_scaleAnimationController.reset();
|
||||
}
|
||||
|
||||
_scaleAnimationController.forward();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_photoViewController.scale == 2.0) {
|
||||
_scaleAnimationController.reverse();
|
||||
return;
|
||||
}
|
||||
|
||||
_photoViewScaleStateController.reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.datas[0]?.isTransitionPage ?? false) {
|
||||
return TransitionViewPaged(data: widget.datas[0]!);
|
||||
}
|
||||
if (widget.datas.length > 1 &&
|
||||
(widget.datas[1]?.isTransitionPage ?? false)) {
|
||||
return TransitionViewPaged(data: widget.datas[1]!);
|
||||
}
|
||||
|
||||
return PhotoViewGallery.builder(
|
||||
backgroundDecoration: const BoxDecoration(color: Colors.transparent),
|
||||
itemCount: 1,
|
||||
builder: (context, _) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
controller: _photoViewController,
|
||||
scaleStateController: _photoViewScaleStateController,
|
||||
basePosition: _scalePosition,
|
||||
onScaleEnd: _onScaleEnd,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onDoubleTapDown: (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
},
|
||||
onDoubleTap: () {},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.datas[0] != null)
|
||||
Flexible(
|
||||
child: ImageViewPaged(
|
||||
data: widget.datas[0]!,
|
||||
loadStateChanged: (state) {
|
||||
if (state.extendedImageLoadState == LoadState.loading) {
|
||||
final ImageChunkEvent? loadingProgress =
|
||||
state.loadingProgress;
|
||||
final double progress =
|
||||
loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: 0;
|
||||
return Container(
|
||||
color: getBackgroundColor(widget.backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: CircularProgressIndicatorAnimateRotate(
|
||||
progress: progress,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state.extendedImageLoadState ==
|
||||
LoadState.completed) {
|
||||
widget.isFailedToLoadImage(false);
|
||||
return Image(image: state.imageProvider);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.failed) {
|
||||
widget.isFailedToLoadImage(true);
|
||||
return Container(
|
||||
color: getBackgroundColor(widget.backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.image_loading_error,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
state.reLoadImage();
|
||||
widget.isFailedToLoadImage(false);
|
||||
},
|
||||
onTap: () {
|
||||
state.reLoadImage();
|
||||
widget.isFailedToLoadImage(false);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Text(l10n.retry),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onLongPressData: (datas) =>
|
||||
widget.onLongPressData.call(datas),
|
||||
),
|
||||
),
|
||||
// if (widget.datas[1] != null) const SizedBox(width: 10),
|
||||
if (widget.datas[1] != null)
|
||||
Flexible(
|
||||
child: ImageViewPaged(
|
||||
data: widget.datas[1]!,
|
||||
loadStateChanged: (state) {
|
||||
if (state.extendedImageLoadState == LoadState.loading) {
|
||||
final ImageChunkEvent? loadingProgress =
|
||||
state.loadingProgress;
|
||||
final double progress =
|
||||
loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: 0;
|
||||
return Container(
|
||||
color: getBackgroundColor(widget.backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: CircularProgressIndicatorAnimateRotate(
|
||||
progress: progress,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state.extendedImageLoadState ==
|
||||
LoadState.completed) {
|
||||
widget.isFailedToLoadImage(false);
|
||||
return Image(image: state.imageProvider);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.failed) {
|
||||
widget.isFailedToLoadImage(true);
|
||||
return Container(
|
||||
color: getBackgroundColor(widget.backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.image_loading_error,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
state.reLoadImage();
|
||||
widget.isFailedToLoadImage(false);
|
||||
},
|
||||
onTap: () {
|
||||
state.reLoadImage();
|
||||
widget.isFailedToLoadImage(false);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Text(l10n.retry),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onLongPressData: (datas) =>
|
||||
widget.onLongPressData.call(datas),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
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/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';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
class DoubleColummVerticalView extends StatelessWidget {
|
||||
final List<UChapDataPreload?> datas;
|
||||
final Function(UChapDataPreload datas) onLongPressData;
|
||||
final BackgroundColor backgroundColor;
|
||||
final Function(bool) isFailedToLoadImage;
|
||||
const DoubleColummVerticalView({
|
||||
super.key,
|
||||
required this.datas,
|
||||
required this.onLongPressData,
|
||||
required this.backgroundColor,
|
||||
required this.isFailedToLoadImage,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
|
||||
if (datas[0]?.isTransitionPage ?? false) {
|
||||
return TransitionViewVertical(data: datas[0]!);
|
||||
}
|
||||
if (datas.length > 1 && (datas[1]?.isTransitionPage ?? false)) {
|
||||
return TransitionViewVertical(data: datas[1]!);
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (datas[0]?.index == 0)
|
||||
SizedBox(height: MediaQuery.of(context).padding.top),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (datas[0] != null)
|
||||
Flexible(
|
||||
child: ImageViewPaged(
|
||||
data: datas[0]!,
|
||||
loadStateChanged: (state) {
|
||||
if (state.extendedImageLoadState == LoadState.loading) {
|
||||
final ImageChunkEvent? loadingProgress =
|
||||
state.loadingProgress;
|
||||
final double progress =
|
||||
loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: 0;
|
||||
return Container(
|
||||
color: getBackgroundColor(backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: CircularProgressIndicatorAnimateRotate(
|
||||
progress: progress,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.completed) {
|
||||
isFailedToLoadImage(false);
|
||||
return Image(image: state.imageProvider);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.failed) {
|
||||
isFailedToLoadImage(true);
|
||||
return Container(
|
||||
color: getBackgroundColor(backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.image_loading_error,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
state.reLoadImage();
|
||||
isFailedToLoadImage(false);
|
||||
},
|
||||
onTap: () {
|
||||
state.reLoadImage();
|
||||
isFailedToLoadImage(false);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Text(l10n.retry),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onLongPressData: (datas) => onLongPressData.call(datas),
|
||||
),
|
||||
),
|
||||
// if (datas[1] != null) const SizedBox(width: 10),
|
||||
if (datas[1] != null)
|
||||
Flexible(
|
||||
child: ImageViewPaged(
|
||||
data: datas[1]!,
|
||||
loadStateChanged: (state) {
|
||||
if (state.extendedImageLoadState == LoadState.loading) {
|
||||
final ImageChunkEvent? loadingProgress =
|
||||
state.loadingProgress;
|
||||
final double progress =
|
||||
loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: 0;
|
||||
return Container(
|
||||
color: getBackgroundColor(backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: CircularProgressIndicatorAnimateRotate(
|
||||
progress: progress,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.completed) {
|
||||
isFailedToLoadImage(false);
|
||||
return Image(image: state.imageProvider);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.failed) {
|
||||
isFailedToLoadImage(true);
|
||||
return Container(
|
||||
color: getBackgroundColor(backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.image_loading_error,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
state.reLoadImage();
|
||||
isFailedToLoadImage(false);
|
||||
},
|
||||
onTap: () {
|
||||
state.reLoadImage();
|
||||
isFailedToLoadImage(false);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Text(l10n.retry),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onLongPressData: (datas) => onLongPressData.call(datas),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/double_columm_view_vertical.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/double_page_view.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/image_view_vertical.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/transition_view_vertical.dart';
|
||||
|
|
@ -134,10 +134,10 @@ class ImageViewWebtoon extends StatelessWidget {
|
|||
behavior: HitTestBehavior.translucent,
|
||||
onDoubleTapDown: (details) => onDoubleTapDown(details.globalPosition),
|
||||
onDoubleTap: onDoubleTap,
|
||||
child: DoubleColummVerticalView(
|
||||
datas: datas,
|
||||
child: DoublePageView.vertical(
|
||||
pages: datas,
|
||||
backgroundColor: backgroundColor,
|
||||
isFailedToLoadImage: onFailedToLoadImage,
|
||||
onFailedToLoadImage: onFailedToLoadImage,
|
||||
onLongPressData: onLongPressData,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
230
lib/modules/manga/reader/managers/chapter_preload_manager.dart
Normal file
230
lib/modules/manga/reader/managers/chapter_preload_manager.dart
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/services/get_chapter_pages.dart';
|
||||
|
||||
/// Manages the preloading and memory of chapters in the manga reader.
|
||||
class ChapterPreloadManager {
|
||||
/// The list of preloaded chapter data
|
||||
final List<UChapDataPreload> _pages = [];
|
||||
|
||||
/// Set of chapter IDs currently in memory
|
||||
final Set<String> _loadedChapterIds = {};
|
||||
|
||||
/// Queue of chapter IDs in order of loading (for LRU eviction)
|
||||
final Queue<String> _chapterLoadOrder = Queue();
|
||||
|
||||
/// Current reading index
|
||||
int _currentIndex = 0;
|
||||
|
||||
/// Flag to prevent concurrent preloading
|
||||
bool _isPreloading = false;
|
||||
|
||||
/// Callbacks
|
||||
void Function()? onPagesUpdated;
|
||||
|
||||
/// Gets the list of pages (read-only)
|
||||
List<UChapDataPreload> get pages => List.unmodifiable(_pages);
|
||||
|
||||
/// Gets the current number of pages
|
||||
int get pageCount => _pages.length;
|
||||
|
||||
/// Gets the current index
|
||||
int get currentIndex => _currentIndex;
|
||||
|
||||
/// Gets the loaded chapter count
|
||||
int get loadedChapterCount => _loadedChapterIds.length;
|
||||
|
||||
/// Sets the current reading index
|
||||
set currentIndex(int value) {
|
||||
if (value >= 0 && value < _pages.length) {
|
||||
_currentIndex = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the manager with the first chapter's pages.
|
||||
void initialize(List<UChapDataPreload> initialPages, int startIndex) {
|
||||
_pages.clear();
|
||||
_loadedChapterIds.clear();
|
||||
_chapterLoadOrder.clear();
|
||||
|
||||
_pages.addAll(initialPages);
|
||||
_currentIndex = startIndex;
|
||||
|
||||
// Track the initial chapter
|
||||
if (initialPages.isNotEmpty) {
|
||||
final chapterId = _getChapterIdentifier(initialPages.first.chapter);
|
||||
if (chapterId != null) {
|
||||
_loadedChapterIds.add(chapterId);
|
||||
_chapterLoadOrder.add(chapterId);
|
||||
}
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[ChapterPreload] Initialized with ${initialPages.length} pages',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a transition page between chapters.
|
||||
UChapDataPreload createTransitionPage({
|
||||
required Chapter currentChapter,
|
||||
required Chapter? nextChapter,
|
||||
required String mangaName,
|
||||
bool isLastChapter = false,
|
||||
}) {
|
||||
return UChapDataPreload.transition(
|
||||
currentChapter: currentChapter,
|
||||
nextChapter: nextChapter,
|
||||
mangaName: mangaName,
|
||||
pageIndex: _pages.length,
|
||||
isLastChapter: isLastChapter,
|
||||
);
|
||||
}
|
||||
|
||||
/// Preloads the next chapter's pages.
|
||||
///
|
||||
/// Returns true if preloading was successful, false otherwise.
|
||||
Future<bool> preloadNextChapter(
|
||||
GetChapterPagesModel chapterData,
|
||||
Chapter currentChapter,
|
||||
) async {
|
||||
if (_isPreloading) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ChapterPreload] Already preloading, skipping');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_isPreloading = true;
|
||||
|
||||
try {
|
||||
if (chapterData.uChapDataPreload.isEmpty) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ChapterPreload] No pages in chapter data');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
final firstPage = chapterData.uChapDataPreload.first;
|
||||
if (firstPage.chapter == null) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ChapterPreload] No chapter in first page');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
final chapterId = _getChapterIdentifier(firstPage.chapter);
|
||||
if (chapterId != null && _loadedChapterIds.contains(chapterId)) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ChapterPreload] Chapter already loaded: $chapterId');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create transition page
|
||||
final transitionPage = createTransitionPage(
|
||||
currentChapter: currentChapter,
|
||||
nextChapter: firstPage.chapter,
|
||||
mangaName: currentChapter.manga.value?.name ?? '',
|
||||
);
|
||||
|
||||
// Update page indices for new pages
|
||||
final startIndex = _pages.length + 1;
|
||||
final newPages = chapterData.uChapDataPreload.asMap().entries.map((
|
||||
entry,
|
||||
) {
|
||||
return entry.value..pageIndex = startIndex + entry.key;
|
||||
}).toList();
|
||||
|
||||
// Add to pages list
|
||||
_pages.add(transitionPage);
|
||||
_pages.addAll(newPages);
|
||||
|
||||
// Track the new chapter
|
||||
if (chapterId != null) {
|
||||
_loadedChapterIds.add(chapterId);
|
||||
_chapterLoadOrder.add(chapterId);
|
||||
}
|
||||
|
||||
// Notify listeners
|
||||
onPagesUpdated?.call();
|
||||
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[ChapterPreload] Added ${newPages.length} pages from next chapter',
|
||||
);
|
||||
debugPrint(
|
||||
'[ChapterPreload] Total pages: ${_pages.length}, Chapters: ${_loadedChapterIds.length}',
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
_isPreloading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a "last chapter" transition page.
|
||||
bool addLastChapterTransition(Chapter chapter) {
|
||||
// Check if already added
|
||||
if (_pages.isNotEmpty && (_pages.last.isLastChapter ?? false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final transitionPage = createTransitionPage(
|
||||
currentChapter: chapter,
|
||||
nextChapter: null,
|
||||
mangaName: chapter.manga.value?.name ?? '',
|
||||
isLastChapter: true,
|
||||
);
|
||||
|
||||
_pages.add(transitionPage);
|
||||
onPagesUpdated?.call();
|
||||
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ChapterPreload] Added last chapter transition');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Updates the cropImage for a page at the given index.
|
||||
void updatePageCropImage(int index, Uint8List? cropImage) {
|
||||
if (index >= 0 && index < _pages.length) {
|
||||
_pages[index].cropImage = cropImage;
|
||||
onPagesUpdated?.call();
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a unique identifier for a chapter.
|
||||
String? _getChapterIdentifier(Chapter? chapter) {
|
||||
if (chapter == null) return null;
|
||||
|
||||
final url = chapter.url?.trim() ?? '';
|
||||
final archivePath = chapter.archivePath?.trim() ?? '';
|
||||
|
||||
if (url.isNotEmpty) return 'url:$url';
|
||||
if (archivePath.isNotEmpty) return 'archive:$archivePath';
|
||||
|
||||
return 'id:${chapter.id}';
|
||||
}
|
||||
|
||||
/// Disposes of all resources.
|
||||
Future<void> dispose() async {
|
||||
// Clear pages
|
||||
_pages.clear();
|
||||
_loadedChapterIds.clear();
|
||||
_chapterLoadOrder.clear();
|
||||
|
||||
// Clear callbacks
|
||||
onPagesUpdated = null;
|
||||
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ChapterPreload] Disposed');
|
||||
}
|
||||
}
|
||||
}
|
||||
212
lib/modules/manga/reader/mixins/reader_gestures.dart
Normal file
212
lib/modules/manga/reader/mixins/reader_gestures.dart
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Widget providing horizontal tap zones for reader navigation.
|
||||
class HorizontalTapZones extends StatelessWidget {
|
||||
/// Callback for left region tap.
|
||||
final VoidCallback onLeftTap;
|
||||
|
||||
/// Callback for center region tap.
|
||||
final VoidCallback onCenterTap;
|
||||
|
||||
/// Callback for right region tap.
|
||||
final VoidCallback onRightTap;
|
||||
|
||||
/// Callback for double-tap with position.
|
||||
final void Function(Offset position)? onDoubleTap;
|
||||
|
||||
/// Whether to show overlay for failed images.
|
||||
final bool showFailedOverlay;
|
||||
|
||||
/// Widget to show when image failed to load.
|
||||
final Widget? failedWidget;
|
||||
|
||||
const HorizontalTapZones({
|
||||
super.key,
|
||||
required this.onLeftTap,
|
||||
required this.onCenterTap,
|
||||
required this.onRightTap,
|
||||
this.onDoubleTap,
|
||||
this.showFailedOverlay = false,
|
||||
this.failedWidget,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
// Left region (2 flex)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _TapZone(onTap: onLeftTap, onDoubleTap: onDoubleTap),
|
||||
),
|
||||
// Center region (2 flex)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: showFailedOverlay && failedWidget != null
|
||||
? failedWidget!
|
||||
: _TapZone(onTap: onCenterTap, onDoubleTap: onDoubleTap),
|
||||
),
|
||||
// Right region (2 flex)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _TapZone(onTap: onRightTap, onDoubleTap: onDoubleTap),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget providing vertical tap zones for reader navigation.
|
||||
class VerticalTapZones extends StatelessWidget {
|
||||
/// Callback for top region tap.
|
||||
final VoidCallback onTopTap;
|
||||
|
||||
/// Callback for center region tap.
|
||||
final VoidCallback onCenterTap;
|
||||
|
||||
/// Callback for bottom region tap.
|
||||
final VoidCallback onBottomTap;
|
||||
|
||||
/// Callback for double-tap with position.
|
||||
final void Function(Offset position)? onDoubleTap;
|
||||
|
||||
const VerticalTapZones({
|
||||
super.key,
|
||||
required this.onTopTap,
|
||||
required this.onCenterTap,
|
||||
required this.onBottomTap,
|
||||
this.onDoubleTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// Top region (2 flex)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _TapZone(onTap: onTopTap, onDoubleTap: onDoubleTap),
|
||||
),
|
||||
// Center region (5 flex) - larger for viewing
|
||||
const Expanded(flex: 5, child: SizedBox.shrink()),
|
||||
// Bottom region (2 flex)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _TapZone(onTap: onBottomTap, onDoubleTap: onDoubleTap),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TapZone extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final void Function(Offset position)? onDoubleTap;
|
||||
|
||||
const _TapZone({required this.onTap, this.onDoubleTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onTap,
|
||||
onDoubleTapDown: onDoubleTap != null
|
||||
? (details) => onDoubleTap!(details.globalPosition)
|
||||
: null,
|
||||
onDoubleTap: onDoubleTap != null ? () {} : null,
|
||||
onSecondaryTapDown: onDoubleTap != null
|
||||
? (details) => onDoubleTap!(details.globalPosition)
|
||||
: null,
|
||||
onSecondaryTap: onDoubleTap != null ? () {} : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for keyboard shortcuts in the reader.
|
||||
class ReaderKeyboardHandler {
|
||||
final VoidCallback? onEscape;
|
||||
final VoidCallback? onFullScreen;
|
||||
final VoidCallback? onPreviousPage;
|
||||
final VoidCallback? onNextPage;
|
||||
final VoidCallback? onNextChapter;
|
||||
final VoidCallback? onPreviousChapter;
|
||||
|
||||
const ReaderKeyboardHandler({
|
||||
this.onEscape,
|
||||
this.onFullScreen,
|
||||
this.onPreviousPage,
|
||||
this.onNextPage,
|
||||
this.onNextChapter,
|
||||
this.onPreviousChapter,
|
||||
});
|
||||
|
||||
/// Handles a key event and returns true if it was handled.
|
||||
bool handleKeyEvent(KeyEvent event, {bool isReverseHorizontal = false}) {
|
||||
if (event is! KeyDownEvent) return false;
|
||||
switch (event.logicalKey) {
|
||||
case LogicalKeyboardKey.f11:
|
||||
onFullScreen?.call();
|
||||
return true;
|
||||
|
||||
case LogicalKeyboardKey.escape:
|
||||
case LogicalKeyboardKey.backspace:
|
||||
onEscape?.call();
|
||||
return true;
|
||||
|
||||
case LogicalKeyboardKey.arrowUp:
|
||||
onPreviousPage?.call();
|
||||
return true;
|
||||
|
||||
case LogicalKeyboardKey.arrowDown:
|
||||
onNextPage?.call();
|
||||
return true;
|
||||
|
||||
case LogicalKeyboardKey.arrowLeft:
|
||||
if (isReverseHorizontal) {
|
||||
onNextPage?.call();
|
||||
} else {
|
||||
onPreviousPage?.call();
|
||||
}
|
||||
return true;
|
||||
|
||||
case LogicalKeyboardKey.arrowRight:
|
||||
if (isReverseHorizontal) {
|
||||
onPreviousPage?.call();
|
||||
} else {
|
||||
onNextPage?.call();
|
||||
}
|
||||
return true;
|
||||
|
||||
case LogicalKeyboardKey.keyN:
|
||||
case LogicalKeyboardKey.pageDown:
|
||||
case LogicalKeyboardKey.shiftRight:
|
||||
onNextChapter?.call();
|
||||
return true;
|
||||
|
||||
case LogicalKeyboardKey.keyP:
|
||||
case LogicalKeyboardKey.pageUp:
|
||||
case LogicalKeyboardKey.shiftLeft:
|
||||
onPreviousChapter?.call();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a KeyboardListener widget with this handler.
|
||||
Widget wrapWithKeyboardListener({
|
||||
required Widget child,
|
||||
bool isReverseHorizontal = false,
|
||||
FocusNode? focusNode,
|
||||
}) {
|
||||
return KeyboardListener(
|
||||
autofocus: true,
|
||||
focusNode: focusNode ?? FocusNode(),
|
||||
onKeyEvent: (event) =>
|
||||
handleKeyEvent(event, isReverseHorizontal: isReverseHorizontal),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
105
lib/modules/manga/reader/mixins/reader_memory_management.dart
Normal file
105
lib/modules/manga/reader/mixins/reader_memory_management.dart
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/managers/chapter_preload_manager.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/services/get_chapter_pages.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
|
||||
mixin ReaderMemoryManagement {
|
||||
/// The preload manager that handles memory-bounded chapter caching.
|
||||
late final ChapterPreloadManager _preloadManager = ChapterPreloadManager();
|
||||
|
||||
/// Whether the preload manager has been initialized.
|
||||
bool _isPreloadManagerInitialized = false;
|
||||
|
||||
/// Gets the preload manager.
|
||||
ChapterPreloadManager get preloadManager => _preloadManager;
|
||||
|
||||
/// Gets all currently loaded pages.
|
||||
List<UChapDataPreload> get pages => _preloadManager.pages;
|
||||
|
||||
/// Gets the total page count.
|
||||
int get pageCount => _preloadManager.pageCount;
|
||||
|
||||
/// Gets the current page index.
|
||||
int get currentPageIndex => _preloadManager.currentIndex;
|
||||
|
||||
/// Sets the current page index.
|
||||
set currentPageIndex(int value) {
|
||||
_preloadManager.currentIndex = value;
|
||||
}
|
||||
|
||||
/// Initializes the preload manager with initial chapter data.
|
||||
///
|
||||
/// [chapterData] - The initial chapter pages to load.
|
||||
/// [startIndex] - The initial page index (default: 0).
|
||||
/// [onPagesUpdated] - Callback when pages are added/removed.
|
||||
/// [onIndexAdjusted] - Callback when current index needs adjustment.
|
||||
void initializePreloadManager(
|
||||
GetChapterPagesModel chapterData, {
|
||||
int startIndex = 0,
|
||||
VoidCallback? onPagesUpdated,
|
||||
}) {
|
||||
if (_isPreloadManagerInitialized) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ReaderMemoryManagement] Already initialized, skipping');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_preloadManager.onPagesUpdated = onPagesUpdated;
|
||||
|
||||
_preloadManager.initialize(chapterData.uChapDataPreload, startIndex);
|
||||
|
||||
_isPreloadManagerInitialized = true;
|
||||
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[ReaderMemoryManagement] Initialized with ${chapterData.uChapDataPreload.length} pages',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Preloads the next chapter with automatic memory management.
|
||||
///
|
||||
/// Unlike the old implementation, this method will automatically
|
||||
/// evict old chapters when the limit is reached.
|
||||
///
|
||||
/// [chapterData] - The chapter data to preload.
|
||||
/// [currentChapter] - The current chapter (for transition page).
|
||||
///
|
||||
/// Returns a Future that completes with `true` if the chapter was preloaded,
|
||||
/// `false` if it was already loaded or if preloading failed.
|
||||
Future<bool> preloadNextChapter(
|
||||
GetChapterPagesModel chapterData,
|
||||
Chapter currentChapter,
|
||||
) async {
|
||||
return await _preloadManager.preloadNextChapter(
|
||||
chapterData,
|
||||
currentChapter,
|
||||
);
|
||||
}
|
||||
|
||||
/// Adds a "last chapter" transition page.
|
||||
///
|
||||
/// Returns `true` if added successfully, `false` if already added.
|
||||
bool addLastChapterTransition(Chapter chapter) {
|
||||
return _preloadManager.addLastChapterTransition(chapter);
|
||||
}
|
||||
|
||||
/// Updates the cropImage for a page at the given index.
|
||||
void updatePageCropImage(int index, Uint8List? cropImage) {
|
||||
_preloadManager.updatePageCropImage(index, cropImage);
|
||||
}
|
||||
|
||||
/// Disposes the preload manager and clears all cached data.
|
||||
Future<void> disposePreloadManager() async {
|
||||
if (!_isPreloadManagerInitialized) return;
|
||||
|
||||
await _preloadManager.dispose();
|
||||
_isPreloadManagerInitialized = false;
|
||||
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ReaderMemoryManagement] Disposed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,11 +10,11 @@ part of 'color_filter_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(CustomColorFilterState)
|
||||
const customColorFilterStateProvider = CustomColorFilterStateProvider._();
|
||||
final customColorFilterStateProvider = CustomColorFilterStateProvider._();
|
||||
|
||||
final class CustomColorFilterStateProvider
|
||||
extends $NotifierProvider<CustomColorFilterState, CustomColorFilter?> {
|
||||
const CustomColorFilterStateProvider._()
|
||||
CustomColorFilterStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -49,7 +49,6 @@ abstract class _$CustomColorFilterState extends $Notifier<CustomColorFilter?> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<CustomColorFilter?, CustomColorFilter?>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -59,17 +58,17 @@ abstract class _$CustomColorFilterState extends $Notifier<CustomColorFilter?> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(EnableCustomColorFilterState)
|
||||
const enableCustomColorFilterStateProvider =
|
||||
final enableCustomColorFilterStateProvider =
|
||||
EnableCustomColorFilterStateProvider._();
|
||||
|
||||
final class EnableCustomColorFilterStateProvider
|
||||
extends $NotifierProvider<EnableCustomColorFilterState, bool> {
|
||||
const EnableCustomColorFilterStateProvider._()
|
||||
EnableCustomColorFilterStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -104,7 +103,6 @@ abstract class _$EnableCustomColorFilterState extends $Notifier<bool> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -114,16 +112,16 @@ abstract class _$EnableCustomColorFilterState extends $Notifier<bool> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ColorFilterBlendModeState)
|
||||
const colorFilterBlendModeStateProvider = ColorFilterBlendModeStateProvider._();
|
||||
final colorFilterBlendModeStateProvider = ColorFilterBlendModeStateProvider._();
|
||||
|
||||
final class ColorFilterBlendModeStateProvider
|
||||
extends $NotifierProvider<ColorFilterBlendModeState, ColorFilterBlendMode> {
|
||||
const ColorFilterBlendModeStateProvider._()
|
||||
ColorFilterBlendModeStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
@ -159,7 +157,6 @@ abstract class _$ColorFilterBlendModeState
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<ColorFilterBlendMode, ColorFilterBlendMode>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -169,6 +166,6 @@ abstract class _$ColorFilterBlendModeState
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'crop_borders_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(cropBorders)
|
||||
const cropBordersProvider = CropBordersFamily._();
|
||||
final cropBordersProvider = CropBordersFamily._();
|
||||
|
||||
final class CropBordersProvider
|
||||
extends
|
||||
|
|
@ -20,7 +20,7 @@ final class CropBordersProvider
|
|||
FutureOr<Uint8List?>
|
||||
>
|
||||
with $FutureModifier<Uint8List?>, $FutureProvider<Uint8List?> {
|
||||
const CropBordersProvider._({
|
||||
CropBordersProvider._({
|
||||
required CropBordersFamily super.from,
|
||||
required ({UChapDataPreload data, bool cropBorder}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -76,7 +76,7 @@ final class CropBordersFamily extends $Family
|
|||
FutureOr<Uint8List?>,
|
||||
({UChapDataPreload data, bool cropBorder})
|
||||
> {
|
||||
const CropBordersFamily._()
|
||||
CropBordersFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'cropBordersProvider',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'manga_reader_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(mangaReader)
|
||||
const mangaReaderProvider = MangaReaderFamily._();
|
||||
final mangaReaderProvider = MangaReaderFamily._();
|
||||
|
||||
final class MangaReaderProvider
|
||||
extends
|
||||
|
|
@ -20,7 +20,7 @@ final class MangaReaderProvider
|
|||
FutureOr<ChapterWithPages>
|
||||
>
|
||||
with $FutureModifier<ChapterWithPages>, $FutureProvider<ChapterWithPages> {
|
||||
const MangaReaderProvider._({
|
||||
MangaReaderProvider._({
|
||||
required MangaReaderFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
|
|
@ -68,7 +68,7 @@ String _$mangaReaderHash() => r'cf8c44c6c3567ba4e9a0e08137ab2f29b71307eb';
|
|||
|
||||
final class MangaReaderFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<ChapterWithPages>, int> {
|
||||
const MangaReaderFamily._()
|
||||
MangaReaderFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'mangaReaderProvider',
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ part of 'reader_controller_provider.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(CurrentIndex)
|
||||
const currentIndexProvider = CurrentIndexFamily._();
|
||||
final currentIndexProvider = CurrentIndexFamily._();
|
||||
|
||||
final class CurrentIndexProvider extends $NotifierProvider<CurrentIndex, int> {
|
||||
const CurrentIndexProvider._({
|
||||
CurrentIndexProvider._({
|
||||
required CurrentIndexFamily super.from,
|
||||
required Chapter super.argument,
|
||||
}) : super(
|
||||
|
|
@ -61,7 +61,7 @@ String _$currentIndexHash() => r'e0b5e5b8a2afa5e8df699784009720334e1c9e80';
|
|||
|
||||
final class CurrentIndexFamily extends $Family
|
||||
with $ClassFamilyOverride<CurrentIndex, int, int, int, Chapter> {
|
||||
const CurrentIndexFamily._()
|
||||
CurrentIndexFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'currentIndexProvider',
|
||||
|
|
@ -85,7 +85,6 @@ abstract class _$CurrentIndex extends $Notifier<int> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(_$args);
|
||||
final ref = this.ref as $Ref<int, int>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -95,16 +94,16 @@ abstract class _$CurrentIndex extends $Notifier<int> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(_$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ReaderController)
|
||||
const readerControllerProvider = ReaderControllerFamily._();
|
||||
final readerControllerProvider = ReaderControllerFamily._();
|
||||
|
||||
final class ReaderControllerProvider
|
||||
extends $NotifierProvider<ReaderController, KeepAliveLink> {
|
||||
const ReaderControllerProvider._({
|
||||
ReaderControllerProvider._({
|
||||
required ReaderControllerFamily super.from,
|
||||
required Chapter super.argument,
|
||||
}) : super(
|
||||
|
|
@ -159,7 +158,7 @@ final class ReaderControllerFamily extends $Family
|
|||
KeepAliveLink,
|
||||
Chapter
|
||||
> {
|
||||
const ReaderControllerFamily._()
|
||||
ReaderControllerFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'readerControllerProvider',
|
||||
|
|
@ -183,7 +182,6 @@ abstract class _$ReaderController extends $Notifier<KeepAliveLink> {
|
|||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(chapter: _$args);
|
||||
final ref = this.ref as $Ref<KeepAliveLink, KeepAliveLink>;
|
||||
final element =
|
||||
ref.element
|
||||
|
|
@ -193,6 +191,6 @@ abstract class _$ReaderController extends $Notifier<KeepAliveLink> {
|
|||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
element.handleCreate(ref, () => build(chapter: _$args));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
138
lib/modules/manga/reader/services/page_navigation_service.dart
Normal file
138
lib/modules/manga/reader/services/page_navigation_service.dart
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
/// Service for handling page navigation in the manga reader.
|
||||
///
|
||||
/// Abstracts the complexity of navigating between different reader modes:
|
||||
/// - Paged modes (vertical, LTR, RTL)
|
||||
/// - Continuous modes (vertical continuous, webtoon, horizontal continuous)
|
||||
class PageNavigationService {
|
||||
final ItemScrollController itemScrollController;
|
||||
final ExtendedPageController extendedController;
|
||||
|
||||
const PageNavigationService({
|
||||
required this.itemScrollController,
|
||||
required this.extendedController,
|
||||
});
|
||||
|
||||
/// Navigates to a specific page index.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [index]: The target page index
|
||||
/// - [readerMode]: Current reader mode
|
||||
/// - [animate]: Whether to animate the transition
|
||||
void navigateToPage({
|
||||
required int index,
|
||||
required ReaderMode readerMode,
|
||||
required bool animate,
|
||||
}) {
|
||||
if (index < 0) return;
|
||||
|
||||
if (_isContinuousMode(readerMode)) {
|
||||
_navigateContinuous(index, animate);
|
||||
} else {
|
||||
_navigatePaged(index, animate);
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigates to next page.
|
||||
void nextPage({
|
||||
required ReaderMode readerMode,
|
||||
required int currentIndex,
|
||||
required int maxPages,
|
||||
required bool animate,
|
||||
}) {
|
||||
if (currentIndex >= maxPages - 1) return;
|
||||
navigateToPage(
|
||||
index: currentIndex + 1,
|
||||
readerMode: readerMode,
|
||||
animate: animate,
|
||||
);
|
||||
}
|
||||
|
||||
/// Navigates to previous page.
|
||||
void previousPage({
|
||||
required ReaderMode readerMode,
|
||||
required int currentIndex,
|
||||
required bool animate,
|
||||
}) {
|
||||
if (currentIndex <= 0) return;
|
||||
navigateToPage(
|
||||
index: currentIndex - 1,
|
||||
readerMode: readerMode,
|
||||
animate: animate,
|
||||
);
|
||||
}
|
||||
|
||||
/// Jumps to a page without animation (for slider).
|
||||
void jumpToPage({required int index, required ReaderMode readerMode}) {
|
||||
if (index < 0) return;
|
||||
|
||||
if (_isContinuousMode(readerMode)) {
|
||||
itemScrollController.jumpTo(index: index);
|
||||
} else {
|
||||
if (extendedController.hasClients) {
|
||||
extendedController.jumpToPage(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _navigateContinuous(int index, bool animate) {
|
||||
if (animate) {
|
||||
itemScrollController.scrollTo(
|
||||
curve: Curves.ease,
|
||||
index: index,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
);
|
||||
} else {
|
||||
itemScrollController.jumpTo(index: index);
|
||||
}
|
||||
}
|
||||
|
||||
void _navigatePaged(int index, bool animate) {
|
||||
if (!extendedController.hasClients) return;
|
||||
|
||||
if (animate) {
|
||||
extendedController.animateToPage(
|
||||
index,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
} else {
|
||||
extendedController.jumpToPage(index);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isContinuousMode(ReaderMode mode) {
|
||||
return mode == ReaderMode.verticalContinuous ||
|
||||
mode == ReaderMode.webtoon ||
|
||||
mode == ReaderMode.horizontalContinuous;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mixin to add page navigation capabilities to reader state.
|
||||
mixin PageNavigationMixin<T extends StatefulWidget> on State<T> {
|
||||
PageNavigationService? _navigationService;
|
||||
|
||||
/// Initializes the navigation service with the required controllers.
|
||||
void initPageNavigation({
|
||||
required ItemScrollController itemScrollController,
|
||||
required ExtendedPageController extendedController,
|
||||
}) {
|
||||
_navigationService = PageNavigationService(
|
||||
itemScrollController: itemScrollController,
|
||||
extendedController: extendedController,
|
||||
);
|
||||
}
|
||||
|
||||
/// Gets the navigation service.
|
||||
PageNavigationService get navigationService {
|
||||
assert(
|
||||
_navigationService != null,
|
||||
'PageNavigationService not initialized. Call initPageNavigation first.',
|
||||
);
|
||||
return _navigationService!;
|
||||
}
|
||||
}
|
||||
59
lib/modules/manga/reader/widgets/auto_scroll_button.dart
Normal file
59
lib/modules/manga/reader/widgets/auto_scroll_button.dart
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Auto-scroll play/pause button for continuous reading modes.
|
||||
///
|
||||
/// Shows a play/pause button at the bottom-right corner when auto-scroll is enabled.
|
||||
/// Only visible in vertical/horizontal continuous modes.
|
||||
class ReaderAutoScrollButton extends StatelessWidget {
|
||||
/// Whether the current mode supports auto-scroll (continuous modes).
|
||||
final bool isContinuousMode;
|
||||
|
||||
/// Whether the UI is currently visible (hide button when UI is hidden).
|
||||
final bool isUiVisible;
|
||||
|
||||
/// ValueNotifier for auto-scroll page setting (user preference).
|
||||
final ValueNotifier<bool> autoScrollPage;
|
||||
|
||||
/// ValueNotifier for auto-scroll running state.
|
||||
final ValueNotifier<bool> autoScroll;
|
||||
|
||||
/// Callback when play/pause is toggled.
|
||||
final VoidCallback onToggle;
|
||||
|
||||
const ReaderAutoScrollButton({
|
||||
super.key,
|
||||
required this.isContinuousMode,
|
||||
required this.isUiVisible,
|
||||
required this.autoScrollPage,
|
||||
required this.autoScroll,
|
||||
required this.onToggle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!isContinuousMode) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: isUiVisible
|
||||
? const SizedBox.shrink()
|
||||
: ValueListenableBuilder(
|
||||
valueListenable: autoScrollPage,
|
||||
builder: (context, isEnabled, child) => isEnabled
|
||||
? ValueListenableBuilder(
|
||||
valueListenable: autoScroll,
|
||||
builder: (context, isPlaying, child) => IconButton(
|
||||
onPressed: onToggle,
|
||||
icon: Icon(
|
||||
isPlaying ? Icons.pause_circle : Icons.play_circle,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
325
lib/modules/manga/reader/widgets/double_page_view.dart
Normal file
325
lib/modules/manga/reader/widgets/double_page_view.dart
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
import 'package:extended_image/extended_image.dart';
|
||||
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/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/manga/reader/widgets/transition_view_vertical.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
|
||||
/// Unified double page view for both paged and continuous reading modes.
|
||||
///
|
||||
/// This replaces both `DoubleColummView` and `DoubleColummVerticalView`
|
||||
/// to eliminate code duplication (previously ~80% identical code).
|
||||
class DoublePageView extends StatefulWidget {
|
||||
/// The two pages to display side by side.
|
||||
final List<UChapDataPreload?> pages;
|
||||
|
||||
/// Callback when an image is long-pressed.
|
||||
final Function(UChapDataPreload data)? onLongPressData;
|
||||
|
||||
/// Background color setting.
|
||||
final BackgroundColor backgroundColor;
|
||||
|
||||
/// Callback for image load failure state.
|
||||
final Function(bool)? onFailedToLoadImage;
|
||||
|
||||
/// Whether to use the paged mode (with PhotoView zoom) or vertical mode.
|
||||
///
|
||||
/// - `true`: Paged mode with pinch-to-zoom support (uses PhotoViewGallery)
|
||||
/// - `false`: Vertical/Continuous mode (simple Column layout)
|
||||
final bool isPagedMode;
|
||||
|
||||
/// Whether to add top padding for the first page (vertical mode only).
|
||||
final bool addTopPadding;
|
||||
|
||||
const DoublePageView({
|
||||
super.key,
|
||||
required this.pages,
|
||||
required this.backgroundColor,
|
||||
this.onLongPressData,
|
||||
this.onFailedToLoadImage,
|
||||
this.isPagedMode = true,
|
||||
this.addTopPadding = true,
|
||||
});
|
||||
|
||||
/// Creates a paged mode double page view.
|
||||
const DoublePageView.paged({
|
||||
super.key,
|
||||
required this.pages,
|
||||
required this.backgroundColor,
|
||||
this.onLongPressData,
|
||||
this.onFailedToLoadImage,
|
||||
}) : isPagedMode = true,
|
||||
addTopPadding = false;
|
||||
|
||||
/// Creates a vertical/continuous mode double page view.
|
||||
const DoublePageView.vertical({
|
||||
super.key,
|
||||
required this.pages,
|
||||
required this.backgroundColor,
|
||||
this.onLongPressData,
|
||||
this.onFailedToLoadImage,
|
||||
this.addTopPadding = true,
|
||||
}) : isPagedMode = false;
|
||||
|
||||
@override
|
||||
State<DoublePageView> createState() => _DoublePageViewState();
|
||||
}
|
||||
|
||||
class _DoublePageViewState extends State<DoublePageView>
|
||||
with TickerProviderStateMixin {
|
||||
// Controllers for paged mode zoom
|
||||
late AnimationController _scaleAnimationController;
|
||||
late Animation<double> _animation;
|
||||
Alignment _scalePosition = Alignment.center;
|
||||
final PhotoViewController _photoViewController = PhotoViewController();
|
||||
final PhotoViewScaleStateController _photoViewScaleStateController =
|
||||
PhotoViewScaleStateController();
|
||||
|
||||
Duration _doubleTapAnimationDuration() {
|
||||
final doubleTapAnimationValue =
|
||||
isar.settings.getSync(227)?.doubleTapAnimationSpeed ?? 1;
|
||||
return switch (doubleTapAnimationValue) {
|
||||
0 => const Duration(milliseconds: 10),
|
||||
1 => const Duration(milliseconds: 800),
|
||||
_ => const Duration(milliseconds: 200),
|
||||
};
|
||||
}
|
||||
|
||||
void _onScaleEnd(
|
||||
BuildContext context,
|
||||
ScaleEndDetails details,
|
||||
PhotoViewControllerValue controllerValue,
|
||||
) {
|
||||
if (controllerValue.scale! < 1) {
|
||||
_photoViewScaleStateController.reset();
|
||||
}
|
||||
}
|
||||
|
||||
double get pixelRatio => View.of(context).devicePixelRatio;
|
||||
Size get size => View.of(context).physicalSize / pixelRatio;
|
||||
|
||||
Alignment _computeAlignmentByTapOffset(Offset offset) {
|
||||
return Alignment(
|
||||
(offset.dx - size.width / 2) / (size.width / 2),
|
||||
(offset.dy - size.height / 2) / (size.height / 2),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.isPagedMode) {
|
||||
_scaleAnimationController = AnimationController(
|
||||
duration: _doubleTapAnimationDuration(),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = Tween(begin: 1.0, end: 2.0).animate(
|
||||
CurvedAnimation(curve: Curves.ease, parent: _scaleAnimationController),
|
||||
);
|
||||
_animation.addListener(() {
|
||||
_photoViewController.scale = _animation.value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.isPagedMode) {
|
||||
_scaleAnimationController.dispose();
|
||||
_photoViewController.dispose();
|
||||
_photoViewScaleStateController.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _toggleScale(Offset tapPosition) {
|
||||
if (!widget.isPagedMode || !mounted) return;
|
||||
|
||||
setState(() {
|
||||
if (_scaleAnimationController.isAnimating) return;
|
||||
|
||||
if (_photoViewController.scale == 1.0) {
|
||||
_scalePosition = _computeAlignmentByTapOffset(tapPosition);
|
||||
|
||||
if (_scaleAnimationController.isCompleted) {
|
||||
_scaleAnimationController.reset();
|
||||
}
|
||||
|
||||
_scaleAnimationController.forward();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_photoViewController.scale == 2.0) {
|
||||
_scaleAnimationController.reverse();
|
||||
return;
|
||||
}
|
||||
|
||||
_photoViewScaleStateController.reset();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Check for transition pages
|
||||
if (_isTransitionPage()) {
|
||||
return _buildTransitionPage();
|
||||
}
|
||||
|
||||
return widget.isPagedMode ? _buildPagedMode() : _buildVerticalMode();
|
||||
}
|
||||
|
||||
bool _isTransitionPage() {
|
||||
return (widget.pages.isNotEmpty &&
|
||||
(widget.pages[0]?.isTransitionPage ?? false)) ||
|
||||
(widget.pages.length > 1 &&
|
||||
(widget.pages[1]?.isTransitionPage ?? false));
|
||||
}
|
||||
|
||||
Widget _buildTransitionPage() {
|
||||
final transitionPage = widget.pages.firstWhere(
|
||||
(p) => p?.isTransitionPage ?? false,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
if (transitionPage == null) return const SizedBox.shrink();
|
||||
|
||||
return widget.isPagedMode
|
||||
? TransitionViewPaged(data: transitionPage)
|
||||
: TransitionViewVertical(data: transitionPage);
|
||||
}
|
||||
|
||||
Widget _buildPagedMode() {
|
||||
return PhotoViewGallery.builder(
|
||||
backgroundDecoration: const BoxDecoration(color: Colors.transparent),
|
||||
itemCount: 1,
|
||||
builder: (context, _) {
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
controller: _photoViewController,
|
||||
scaleStateController: _photoViewScaleStateController,
|
||||
basePosition: _scalePosition,
|
||||
onScaleEnd: _onScaleEnd,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onDoubleTapDown: (details) => _toggleScale(details.globalPosition),
|
||||
onDoubleTap: () {},
|
||||
child: _buildPageRow(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVerticalMode() {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Add top padding for first page
|
||||
if (widget.addTopPadding && widget.pages[0]?.index == 0)
|
||||
SizedBox(height: MediaQuery.of(context).padding.top),
|
||||
_buildPageRow(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageRow() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.pages.isNotEmpty && widget.pages[0] != null)
|
||||
Flexible(child: _buildPageImage(widget.pages[0]!)),
|
||||
if (widget.pages.length > 1 && widget.pages[1] != null)
|
||||
Flexible(child: _buildPageImage(widget.pages[1]!)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageImage(UChapDataPreload pageData) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final onLongPress = widget.onLongPressData ?? (_) {};
|
||||
|
||||
return ImageViewPaged(
|
||||
data: pageData,
|
||||
loadStateChanged: (state) {
|
||||
switch (state.extendedImageLoadState) {
|
||||
case LoadState.loading:
|
||||
return _buildLoadingState(state);
|
||||
case LoadState.completed:
|
||||
return _buildCompletedState(state);
|
||||
case LoadState.failed:
|
||||
return _buildFailedState(state, l10n);
|
||||
}
|
||||
},
|
||||
onLongPressData: onLongPress,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingState(ExtendedImageState state) {
|
||||
final loadingProgress = state.loadingProgress;
|
||||
final progress = loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: 0.0;
|
||||
|
||||
return Container(
|
||||
color: getBackgroundColor(widget.backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: CircularProgressIndicatorAnimateRotate(progress: progress),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompletedState(ExtendedImageState state) {
|
||||
widget.onFailedToLoadImage?.call(false);
|
||||
return Image(image: state.imageProvider);
|
||||
}
|
||||
|
||||
Widget _buildFailedState(ExtendedImageState state, dynamic l10n) {
|
||||
widget.onFailedToLoadImage?.call(true);
|
||||
|
||||
return Container(
|
||||
color: getBackgroundColor(widget.backgroundColor),
|
||||
height: context.height(0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.image_loading_error,
|
||||
style: TextStyle(color: Colors.white.withValues(alpha: 0.7)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: _buildRetryButton(state, l10n),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRetryButton(ExtendedImageState state, dynamic l10n) {
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
state.reLoadImage();
|
||||
widget.onFailedToLoadImage?.call(false);
|
||||
},
|
||||
onTap: () {
|
||||
state.reLoadImage();
|
||||
widget.onFailedToLoadImage?.call(false);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: Text(l10n.retry),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
229
lib/modules/manga/reader/widgets/image_actions_dialog.dart
Normal file
229
lib/modules/manga/reader/widgets/image_actions_dialog.dart
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/modules/library/providers/local_archive.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/utils/extensions/others.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// Bottom sheet dialog for long-press actions on manga images.
|
||||
///
|
||||
/// Provides options to:
|
||||
/// - Set image as cover
|
||||
/// - Share image
|
||||
/// - Save image to gallery
|
||||
class ImageActionsDialog {
|
||||
/// Shows the image actions dialog.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [context]: Build context
|
||||
/// - [data]: The page data containing the image
|
||||
/// - [manga]: The manga the image belongs to
|
||||
/// - [chapterName]: Name of the chapter (for file naming)
|
||||
static Future<void> show({
|
||||
required BuildContext context,
|
||||
required UChapDataPreload data,
|
||||
required Manga manga,
|
||||
required String chapterName,
|
||||
}) async {
|
||||
final imageBytes = await data.getImageBytes;
|
||||
if (imageBytes == null || !context.mounted) return;
|
||||
|
||||
final name = "${manga.name} $chapterName - ${data.pageIndex}".replaceAll(
|
||||
RegExp(r'[^a-zA-Z0-9 .()\-\s]'),
|
||||
'_',
|
||||
);
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
constraints: BoxConstraints(maxWidth: context.width(1)),
|
||||
builder: (context) => _ImageActionsSheet(
|
||||
imageBytes: imageBytes,
|
||||
manga: manga,
|
||||
fileName: name,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ImageActionsSheet extends StatelessWidget {
|
||||
final List<int> imageBytes;
|
||||
final Manga manga;
|
||||
final String fileName;
|
||||
|
||||
const _ImageActionsSheet({
|
||||
required this.imageBytes,
|
||||
required this.manga,
|
||||
required this.fileName,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SuperListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: context.themeData.scaffoldBackgroundColor,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Handle bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
height: 7,
|
||||
width: 35,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: context.secondaryColor.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Action buttons
|
||||
Row(
|
||||
children: [
|
||||
_ActionButton(
|
||||
label: context.l10n.set_as_cover,
|
||||
icon: Icons.image_outlined,
|
||||
onPressed: () => _setAsCover(context),
|
||||
),
|
||||
_ActionButton(
|
||||
label: context.l10n.share,
|
||||
icon: Icons.share_outlined,
|
||||
onPressed: () => _shareImage(context),
|
||||
),
|
||||
_ActionButton(
|
||||
label: context.l10n.save,
|
||||
icon: Icons.save_outlined,
|
||||
onPressed: () => _saveImage(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _setAsCover(BuildContext context) async {
|
||||
final res = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
content: Text(context.l10n.use_this_as_cover_art),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(
|
||||
manga
|
||||
..customCoverImage = Uint8List.fromList(
|
||||
imageBytes,
|
||||
).getCoverImage
|
||||
..updatedAt = DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
});
|
||||
Navigator.pop(context, "ok");
|
||||
},
|
||||
child: Text(context.l10n.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (res == "ok" && context.mounted) {
|
||||
Navigator.pop(context);
|
||||
botToast(context.l10n.cover_updated, second: 3);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _shareImage(BuildContext context) async {
|
||||
if (!context.mounted) return;
|
||||
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [
|
||||
XFile.fromData(
|
||||
Uint8List.fromList(imageBytes),
|
||||
name: fileName,
|
||||
mimeType: 'image/png',
|
||||
),
|
||||
],
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveImage(BuildContext context) async {
|
||||
final dir = await StorageProvider().getGalleryDirectory();
|
||||
if (dir == null) return;
|
||||
|
||||
final file = File(p.join(dir.path, "$fileName.png"));
|
||||
file.writeAsBytesSync(imageBytes);
|
||||
|
||||
if (context.mounted) {
|
||||
botToast(context.l10n.picture_saved, second: 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionButton extends StatelessWidget {
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const _ActionButton({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
shadowColor: Colors.transparent,
|
||||
),
|
||||
onPressed: onPressed,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(padding: const EdgeInsets.all(4), child: Icon(icon)),
|
||||
Text(label),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
60
lib/modules/manga/reader/widgets/page_indicator.dart
Normal file
60
lib/modules/manga/reader/widgets/page_indicator.dart
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
|
||||
/// Page indicator widget showing current page / total pages.
|
||||
///
|
||||
/// Displayed at the bottom center when the UI is hidden and
|
||||
/// "show page numbers" setting is enabled.
|
||||
class PageIndicator extends ConsumerWidget {
|
||||
/// The current chapter being read.
|
||||
final Chapter chapter;
|
||||
|
||||
/// Whether the UI overlay is currently visible.
|
||||
final bool isUiVisible;
|
||||
|
||||
/// Total number of pages.
|
||||
final int totalPages;
|
||||
|
||||
/// Function to format the current index for display.
|
||||
final String Function(int index) formatCurrentIndex;
|
||||
|
||||
const PageIndicator({
|
||||
super.key,
|
||||
required this.chapter,
|
||||
required this.isUiVisible,
|
||||
required this.totalPages,
|
||||
required this.formatCurrentIndex,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final currentIndex = ref.watch(currentIndexProvider(chapter));
|
||||
final showPagesNumber = ref.watch(showPagesNumberStateProvider);
|
||||
|
||||
// Don't show when UI is visible or setting is disabled
|
||||
if (isUiVisible || !showPagesNumber) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Text(
|
||||
'${formatCurrentIndex(currentIndex)} / $totalPages',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.0,
|
||||
shadows: [
|
||||
Shadow(offset: Offset(-1, -1), blurRadius: 1),
|
||||
Shadow(offset: Offset(1, -1), blurRadius: 1),
|
||||
Shadow(offset: Offset(1, 1), blurRadius: 1),
|
||||
Shadow(offset: Offset(-1, 1), blurRadius: 1),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
156
lib/modules/manga/reader/widgets/reader_app_bar.dart
Normal file
156
lib/modules/manga/reader/widgets/reader_app_bar.dart
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/widgets/btn_chapter_list_dialog.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
import 'package:mangayomi/utils/utils.dart';
|
||||
|
||||
/// The app bar for the manga reader.
|
||||
///
|
||||
/// Displays:
|
||||
/// - Back button
|
||||
/// - Manga name and chapter title
|
||||
/// - Chapter list button
|
||||
/// - Bookmark button
|
||||
/// - Web view button (for non-local sources)
|
||||
///
|
||||
/// This widget is designed to be used directly in reader_view.dart
|
||||
/// as a drop-in replacement for the _appBar() method.
|
||||
class ReaderAppBar extends ConsumerWidget {
|
||||
/// The chapter being read
|
||||
final Chapter chapter;
|
||||
|
||||
/// The manga name to display
|
||||
final String mangaName;
|
||||
|
||||
/// The chapter title to display
|
||||
final String chapterTitle;
|
||||
|
||||
/// Whether the app bar is visible
|
||||
final bool isVisible;
|
||||
|
||||
/// Whether the chapter is bookmarked
|
||||
final bool isBookmarked;
|
||||
|
||||
/// Callback when back button is pressed
|
||||
final VoidCallback onBackPressed;
|
||||
|
||||
/// Callback when bookmark button is pressed
|
||||
final VoidCallback onBookmarkPressed;
|
||||
|
||||
/// Callback when web view button is pressed
|
||||
final VoidCallback? onWebViewPressed;
|
||||
|
||||
/// Background color getter
|
||||
final Color Function(BuildContext) backgroundColor;
|
||||
|
||||
const ReaderAppBar({
|
||||
super.key,
|
||||
required this.chapter,
|
||||
required this.mangaName,
|
||||
required this.chapterTitle,
|
||||
required this.isVisible,
|
||||
required this.isBookmarked,
|
||||
required this.onBackPressed,
|
||||
required this.onBookmarkPressed,
|
||||
this.onWebViewPressed,
|
||||
required this.backgroundColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fullScreenReader = ref.watch(fullScreenReaderStateProvider);
|
||||
final isDesktop =
|
||||
Platform.isMacOS || Platform.isLinux || Platform.isWindows;
|
||||
final isLocalArchive = chapter.manga.value?.isLocalArchive ?? false;
|
||||
|
||||
double height = isVisible
|
||||
? Platform.isIOS
|
||||
? 120.0
|
||||
: !fullScreenReader && !isDesktop
|
||||
? 55.0
|
||||
: 80.0
|
||||
: 0.0;
|
||||
|
||||
return Positioned(
|
||||
top: 0,
|
||||
child: AnimatedContainer(
|
||||
width: context.width(1),
|
||||
height: height,
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: PreferredSize(
|
||||
preferredSize: Size.fromHeight(height),
|
||||
child: AppBar(
|
||||
centerTitle: false,
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 0,
|
||||
leading: BackButton(onPressed: onBackPressed),
|
||||
title: _buildTitle(context),
|
||||
actions: _buildActions(context, isLocalArchive),
|
||||
backgroundColor: backgroundColor(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTitle(BuildContext context) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: SizedBox(
|
||||
width: context.width(0.8),
|
||||
child: Text(
|
||||
'$mangaName ',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
subtitle: SizedBox(
|
||||
width: context.width(0.8),
|
||||
child: Text(
|
||||
chapterTitle,
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w400),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildActions(BuildContext context, bool isLocalArchive) {
|
||||
return [
|
||||
// Chapter list button
|
||||
btnToShowChapterListDialog(context, context.l10n.chapters, chapter),
|
||||
|
||||
// Bookmark button
|
||||
IconButton(
|
||||
onPressed: onBookmarkPressed,
|
||||
icon: Icon(
|
||||
isBookmarked ? Icons.bookmark : Icons.bookmark_border_outlined,
|
||||
),
|
||||
),
|
||||
|
||||
// Web view button (only for non-local sources)
|
||||
if (!isLocalArchive && onWebViewPressed != null)
|
||||
IconButton(onPressed: onWebViewPressed, icon: const Icon(Icons.public)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the web view navigation data.
|
||||
Map<String, dynamic>? buildWebViewData(Chapter chapter) {
|
||||
final manga = chapter.manga.value;
|
||||
if (manga == null) return null;
|
||||
|
||||
final source = getSource(manga.lang!, manga.source!, manga.sourceId);
|
||||
if (source == null) return null;
|
||||
|
||||
final url = "${source.baseUrl}${chapter.url!.getUrlWithoutDomain}";
|
||||
|
||||
return {'url': url, 'sourceId': source.id.toString(), 'title': chapter.name!};
|
||||
}
|
||||
479
lib/modules/manga/reader/widgets/reader_bottom_bar.dart
Normal file
479
lib/modules/manga/reader/widgets/reader_bottom_bar.dart
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/misc.dart' show ProviderListenable;
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.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/more/settings/reader/reader_screen.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/utils/global_style.dart';
|
||||
|
||||
/// The bottom bar for the manga reader.
|
||||
///
|
||||
/// This is a complete drop-in replacement for the _bottomBar() method in reader_view.dart.
|
||||
/// It handles all the complex interactions including:
|
||||
/// - Page slider with real-time updates via Consumer
|
||||
/// - Chapter navigation
|
||||
/// - Reader mode selection
|
||||
/// - Crop borders toggle
|
||||
/// - Double page mode toggle
|
||||
/// - Settings access
|
||||
class ReaderBottomBar extends ConsumerWidget {
|
||||
/// The chapter being read
|
||||
final Chapter chapter;
|
||||
|
||||
/// Whether the bar is visible
|
||||
final bool isVisible;
|
||||
|
||||
/// Whether there is a previous chapter
|
||||
final bool hasPreviousChapter;
|
||||
|
||||
/// Whether there is a next chapter
|
||||
final bool hasNextChapter;
|
||||
|
||||
/// Callback when previous chapter button is pressed
|
||||
final VoidCallback? onPreviousChapter;
|
||||
|
||||
/// Callback when next chapter button is pressed
|
||||
final VoidCallback? onNextChapter;
|
||||
|
||||
/// Callback when slider value changes (for updating provider)
|
||||
final void Function(int value, WidgetRef ref) onSliderChanged;
|
||||
|
||||
/// Callback when slider drag ends (for navigation)
|
||||
final void Function(int value) onSliderChangeEnd;
|
||||
|
||||
/// Callback when reader mode is changed
|
||||
final void Function(ReaderMode mode, WidgetRef ref) onReaderModeChanged;
|
||||
|
||||
/// Callback when page mode toggle button is pressed
|
||||
final VoidCallback? onPageModeToggle;
|
||||
|
||||
/// Callback when settings button is pressed
|
||||
final VoidCallback onSettingsPressed;
|
||||
|
||||
/// Provider for watching current reader mode
|
||||
/// Accepts any ProviderListenable that returns ReaderMode?
|
||||
/// (StateProvider, NotifierProvider, etc.)
|
||||
final ProviderListenable<ReaderMode?> currentReaderModeProvider;
|
||||
|
||||
/// Provider family for watching current page index
|
||||
/// Type: CurrentIndexFamily (from reader_controller_provider.g.dart)
|
||||
final CurrentIndexFamily currentIndexProvider;
|
||||
|
||||
/// Current page mode (nullable for safety)
|
||||
final PageMode? currentPageMode;
|
||||
|
||||
/// Whether RTL reading direction is active
|
||||
final bool isReverseHorizontal;
|
||||
|
||||
/// Total number of pages in current chapter
|
||||
final int totalPages;
|
||||
|
||||
/// Function to get current page index label
|
||||
final String Function(int currentIndex) currentIndexLabel;
|
||||
|
||||
/// Background color getter
|
||||
final Color Function(BuildContext) backgroundColor;
|
||||
|
||||
const ReaderBottomBar({
|
||||
super.key,
|
||||
required this.chapter,
|
||||
required this.isVisible,
|
||||
required this.hasPreviousChapter,
|
||||
required this.hasNextChapter,
|
||||
this.onPreviousChapter,
|
||||
this.onNextChapter,
|
||||
required this.onSliderChanged,
|
||||
required this.onSliderChangeEnd,
|
||||
required this.onReaderModeChanged,
|
||||
this.onPageModeToggle,
|
||||
required this.onSettingsPressed,
|
||||
required this.currentReaderModeProvider,
|
||||
required this.currentIndexProvider,
|
||||
required this.currentPageMode,
|
||||
required this.isReverseHorizontal,
|
||||
required this.totalPages,
|
||||
required this.currentIndexLabel,
|
||||
required this.backgroundColor,
|
||||
});
|
||||
|
||||
bool get _isDoublePageMode => currentPageMode == PageMode.doublePage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final readerMode = ref.watch(currentReaderModeProvider);
|
||||
final isHorizontalContinuous =
|
||||
readerMode == ReaderMode.horizontalContinuous;
|
||||
|
||||
return Positioned(
|
||||
bottom: 0,
|
||||
child: AnimatedContainer(
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: context.width(1),
|
||||
height: isVisible ? 130 : 0,
|
||||
child: Column(
|
||||
children: [
|
||||
// Page slider section
|
||||
Flexible(
|
||||
child: _buildPageSlider(context, ref, isHorizontalContinuous),
|
||||
),
|
||||
|
||||
// Quick actions section
|
||||
Flexible(
|
||||
child: _buildQuickActions(
|
||||
context,
|
||||
ref,
|
||||
readerMode,
|
||||
isHorizontalContinuous,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageSlider(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
bool isHorizontalContinuous,
|
||||
) {
|
||||
return Transform.scale(
|
||||
scaleX: !isReverseHorizontal ? 1 : -1,
|
||||
child: Row(
|
||||
children: [
|
||||
// Previous chapter button
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CircleAvatar(
|
||||
radius: 23,
|
||||
backgroundColor: backgroundColor(context),
|
||||
child: IconButton(
|
||||
onPressed: hasPreviousChapter ? onPreviousChapter : null,
|
||||
icon: Transform.scale(
|
||||
scaleX: 1,
|
||||
child: Icon(
|
||||
Icons.skip_previous_rounded,
|
||||
color: hasPreviousChapter
|
||||
? Theme.of(context).textTheme.bodyLarge!.color
|
||||
: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Slider container
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Container(
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor(context),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Current page label
|
||||
Transform.scale(
|
||||
scaleX: !isReverseHorizontal ? 1 : -1,
|
||||
child: SizedBox(
|
||||
width: 55,
|
||||
child: Center(
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final currentIndex = ref.watch(
|
||||
currentIndexProvider(chapter),
|
||||
);
|
||||
return Text(
|
||||
currentIndexLabel(currentIndex),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Slider
|
||||
if (isVisible)
|
||||
Flexible(
|
||||
flex: 14,
|
||||
child: _buildSlider(
|
||||
context,
|
||||
ref,
|
||||
isHorizontalContinuous,
|
||||
),
|
||||
),
|
||||
|
||||
// Total pages label
|
||||
Transform.scale(
|
||||
scaleX: !isReverseHorizontal ? 1 : -1,
|
||||
child: SizedBox(
|
||||
width: 55,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"$totalPages",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Next chapter button
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CircleAvatar(
|
||||
radius: 23,
|
||||
backgroundColor: backgroundColor(context),
|
||||
child: IconButton(
|
||||
onPressed: hasNextChapter ? onNextChapter : null,
|
||||
icon: Transform.scale(
|
||||
scaleX: 1,
|
||||
child: Icon(
|
||||
Icons.skip_next_rounded,
|
||||
color: hasNextChapter
|
||||
? Theme.of(context).textTheme.bodyLarge!.color
|
||||
: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSlider(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
bool isHorizontalContinuous,
|
||||
) {
|
||||
return Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final currentIndex = ref.watch(currentIndexProvider(chapter));
|
||||
|
||||
final maxValue = (_isDoublePageMode && !isHorizontalContinuous)
|
||||
? ((totalPages / 2).ceil() + 1).toDouble()
|
||||
: (totalPages - 1).toDouble();
|
||||
|
||||
final divisions = totalPages == 1
|
||||
? null
|
||||
: _isDoublePageMode
|
||||
? (totalPages / 2).ceil() + 1
|
||||
: totalPages - 1;
|
||||
|
||||
final currentValue = min(
|
||||
currentIndex.toDouble(),
|
||||
(_isDoublePageMode && !isHorizontalContinuous)
|
||||
? ((totalPages / 2).ceil() + 1).toDouble()
|
||||
: totalPages.toDouble(),
|
||||
);
|
||||
|
||||
return SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
valueIndicatorShape: CustomValueIndicatorShape(
|
||||
tranform: isReverseHorizontal,
|
||||
),
|
||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 5.0),
|
||||
),
|
||||
child: Slider(
|
||||
onChanged: (value) {
|
||||
onSliderChanged(value.toInt(), ref);
|
||||
},
|
||||
onChangeEnd: (newValue) {
|
||||
onSliderChangeEnd(newValue.toInt());
|
||||
},
|
||||
divisions: divisions,
|
||||
value: currentValue,
|
||||
label: currentIndexLabel(currentIndex),
|
||||
min: 0,
|
||||
max: maxValue,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickActions(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ReaderMode? readerMode,
|
||||
bool isHorizontalContinuous,
|
||||
) {
|
||||
return Container(
|
||||
height: 65,
|
||||
color: backgroundColor(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
// Reader mode button
|
||||
PopupMenuButton<ReaderMode>(
|
||||
popUpAnimationStyle: popupAnimationStyle,
|
||||
color: Colors.black,
|
||||
onSelected: (value) {
|
||||
onReaderModeChanged(value, ref);
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
for (var mode in ReaderMode.values)
|
||||
PopupMenuItem(
|
||||
value: mode,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: readerMode == mode
|
||||
? Colors.white
|
||||
: Colors.transparent,
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
Text(
|
||||
getReaderModeName(mode, context),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const Icon(Icons.app_settings_alt_outlined),
|
||||
),
|
||||
|
||||
// Crop borders button
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final cropBorders = ref.watch(cropBordersStateProvider);
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
ref.read(cropBordersStateProvider.notifier).set(!cropBorders);
|
||||
},
|
||||
icon: Stack(
|
||||
children: [
|
||||
const Icon(Icons.crop_rounded),
|
||||
if (!cropBorders)
|
||||
Positioned(
|
||||
right: 8,
|
||||
child: Transform.scale(
|
||||
scaleX: 2.5,
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('\\', style: TextStyle(fontSize: 17)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Double page mode button
|
||||
IconButton(
|
||||
onPressed: !isHorizontalContinuous ? onPageModeToggle : null,
|
||||
icon: Icon(
|
||||
_isDoublePageMode
|
||||
? CupertinoIcons.book_solid
|
||||
: CupertinoIcons.book,
|
||||
),
|
||||
),
|
||||
|
||||
// Settings button
|
||||
IconButton(
|
||||
onPressed: onSettingsPressed,
|
||||
icon: const Icon(Icons.settings_rounded),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget to display the current page number when UI is hidden.
|
||||
class PageNumberOverlay extends StatelessWidget {
|
||||
final int currentIndex;
|
||||
final int totalPages;
|
||||
final bool isVisible;
|
||||
final bool showPageNumbers;
|
||||
final PageMode pageMode;
|
||||
|
||||
const PageNumberOverlay({
|
||||
super.key,
|
||||
required this.currentIndex,
|
||||
required this.totalPages,
|
||||
required this.isVisible,
|
||||
required this.showPageNumbers,
|
||||
required this.pageMode,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isVisible || !showPageNumbers) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final label = pageMode == PageMode.doublePage && currentIndex > 0
|
||||
? _getDoublePageLabel()
|
||||
: '${currentIndex + 1}';
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'$label / $totalPages',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.0,
|
||||
shadows: [
|
||||
Shadow(offset: Offset(-1, -1), blurRadius: 1),
|
||||
Shadow(offset: Offset(1, -1), blurRadius: 1),
|
||||
Shadow(offset: Offset(1, 1), blurRadius: 1),
|
||||
Shadow(offset: Offset(-1, 1), blurRadius: 1),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getDoublePageLabel() {
|
||||
final index1 = currentIndex * 2;
|
||||
final index2 = index1 + 1;
|
||||
|
||||
if (index1 >= totalPages) {
|
||||
return '$totalPages';
|
||||
}
|
||||
|
||||
return index2 >= totalPages ? '$totalPages' : '$index1-$index2';
|
||||
}
|
||||
}
|
||||
222
lib/modules/manga/reader/widgets/reader_gesture_handler.dart
Normal file
222
lib/modules/manga/reader/widgets/reader_gesture_handler.dart
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
/// Manages gesture detection zones and tap handling for the reader.
|
||||
///
|
||||
/// The reader screen is divided into zones:
|
||||
///
|
||||
/// For horizontal reading (LTR):
|
||||
/// ```
|
||||
/// ┌─────────────────────────┐
|
||||
/// │ TOP (prev page) │
|
||||
/// ├───────┬───────┬─────────┤
|
||||
/// │ LEFT │CENTER │ RIGHT │
|
||||
/// │(prev) │ (UI) │ (next) │
|
||||
/// ├───────┴───────┴─────────┤
|
||||
/// │ BOTTOM (next page) │
|
||||
/// └─────────────────────────┘
|
||||
/// ```
|
||||
///
|
||||
/// For RTL mode, LEFT and RIGHT actions are reversed.
|
||||
class ReaderGestureHandler extends StatelessWidget {
|
||||
/// Whether tap zones are enabled for navigation
|
||||
final bool usePageTapZones;
|
||||
|
||||
/// Whether the reader is in RTL mode
|
||||
final bool isRTL;
|
||||
|
||||
/// Whether there's an image loading error
|
||||
final bool hasImageError;
|
||||
|
||||
/// Whether the reader is in continuous scroll mode
|
||||
final bool isContinuousMode;
|
||||
|
||||
/// Callback when UI should be toggled
|
||||
final VoidCallback onToggleUI;
|
||||
|
||||
/// Callback to go to previous page
|
||||
final VoidCallback onPreviousPage;
|
||||
|
||||
/// Callback to go to next page
|
||||
final VoidCallback onNextPage;
|
||||
|
||||
/// Callback for double-tap to zoom (with position)
|
||||
final void Function(Offset position)? onDoubleTapDown;
|
||||
|
||||
/// Callback for double-tap gesture complete
|
||||
final VoidCallback? onDoubleTap;
|
||||
|
||||
/// Callback for secondary tap (right-click on desktop)
|
||||
final void Function(Offset position)? onSecondaryTapDown;
|
||||
|
||||
/// Callback for secondary tap complete
|
||||
final VoidCallback? onSecondaryTap;
|
||||
|
||||
const ReaderGestureHandler({
|
||||
super.key,
|
||||
required this.usePageTapZones,
|
||||
required this.isRTL,
|
||||
required this.hasImageError,
|
||||
required this.isContinuousMode,
|
||||
required this.onToggleUI,
|
||||
required this.onPreviousPage,
|
||||
required this.onNextPage,
|
||||
this.onDoubleTapDown,
|
||||
this.onDoubleTap,
|
||||
this.onSecondaryTapDown,
|
||||
this.onSecondaryTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
// Horizontal zones (left, center, right)
|
||||
_buildHorizontalZones(context),
|
||||
|
||||
// Vertical zones (top, center, bottom)
|
||||
_buildVerticalZones(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHorizontalZones(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
// Left zone
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _ZoneGestureDetector(
|
||||
onTap: () {
|
||||
if (usePageTapZones) {
|
||||
isRTL ? onNextPage() : onPreviousPage();
|
||||
} else {
|
||||
onToggleUI();
|
||||
}
|
||||
},
|
||||
onDoubleTapDown: isContinuousMode ? onDoubleTapDown : null,
|
||||
onDoubleTap: isContinuousMode ? onDoubleTap : null,
|
||||
onSecondaryTapDown: isContinuousMode ? onSecondaryTapDown : null,
|
||||
onSecondaryTap: isContinuousMode ? onSecondaryTap : null,
|
||||
),
|
||||
),
|
||||
|
||||
// Center zone
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: hasImageError
|
||||
? SizedBox(width: context.width(1), height: context.height(0.7))
|
||||
: _ZoneGestureDetector(
|
||||
onTap: onToggleUI,
|
||||
onDoubleTapDown: isContinuousMode ? onDoubleTapDown : null,
|
||||
onDoubleTap: isContinuousMode ? onDoubleTap : null,
|
||||
onSecondaryTapDown: isContinuousMode
|
||||
? onSecondaryTapDown
|
||||
: null,
|
||||
onSecondaryTap: isContinuousMode ? onSecondaryTap : null,
|
||||
),
|
||||
),
|
||||
|
||||
// Right zone
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _ZoneGestureDetector(
|
||||
onTap: () {
|
||||
if (usePageTapZones) {
|
||||
isRTL ? onPreviousPage() : onNextPage();
|
||||
} else {
|
||||
onToggleUI();
|
||||
}
|
||||
},
|
||||
onDoubleTapDown: isContinuousMode ? onDoubleTapDown : null,
|
||||
onDoubleTap: isContinuousMode ? onDoubleTap : null,
|
||||
onSecondaryTapDown: isContinuousMode ? onSecondaryTapDown : null,
|
||||
onSecondaryTap: isContinuousMode ? onSecondaryTap : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVerticalZones(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// Top zone
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _ZoneGestureDetector(
|
||||
onTap: () {
|
||||
if (hasImageError) {
|
||||
onToggleUI();
|
||||
} else if (usePageTapZones) {
|
||||
onPreviousPage();
|
||||
} else {
|
||||
onToggleUI();
|
||||
}
|
||||
},
|
||||
onDoubleTapDown: isContinuousMode ? onDoubleTapDown : null,
|
||||
onDoubleTap: isContinuousMode ? onDoubleTap : null,
|
||||
onSecondaryTapDown: isContinuousMode ? onSecondaryTapDown : null,
|
||||
onSecondaryTap: isContinuousMode ? onSecondaryTap : null,
|
||||
),
|
||||
),
|
||||
|
||||
// Center zone (transparent, handled by horizontal zones)
|
||||
const Expanded(flex: 5, child: SizedBox.shrink()),
|
||||
|
||||
// Bottom zone
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _ZoneGestureDetector(
|
||||
onTap: () {
|
||||
if (hasImageError) {
|
||||
onToggleUI();
|
||||
} else if (usePageTapZones) {
|
||||
onNextPage();
|
||||
} else {
|
||||
onToggleUI();
|
||||
}
|
||||
},
|
||||
onDoubleTapDown: isContinuousMode ? onDoubleTapDown : null,
|
||||
onDoubleTap: isContinuousMode ? onDoubleTap : null,
|
||||
onSecondaryTapDown: isContinuousMode ? onSecondaryTapDown : null,
|
||||
onSecondaryTap: isContinuousMode ? onSecondaryTap : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Individual gesture detector for a zone.
|
||||
class _ZoneGestureDetector extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final void Function(Offset position)? onDoubleTapDown;
|
||||
final VoidCallback? onDoubleTap;
|
||||
final void Function(Offset position)? onSecondaryTapDown;
|
||||
final VoidCallback? onSecondaryTap;
|
||||
|
||||
const _ZoneGestureDetector({
|
||||
required this.onTap,
|
||||
this.onDoubleTapDown,
|
||||
this.onDoubleTap,
|
||||
this.onSecondaryTapDown,
|
||||
this.onSecondaryTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onTap,
|
||||
onDoubleTapDown: onDoubleTapDown != null
|
||||
? (details) => onDoubleTapDown!(details.globalPosition)
|
||||
: null,
|
||||
onDoubleTap: onDoubleTap,
|
||||
onSecondaryTapDown: onSecondaryTapDown != null
|
||||
? (details) => onSecondaryTapDown!(details.globalPosition)
|
||||
: null,
|
||||
onSecondaryTap: onSecondaryTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
425
lib/modules/manga/reader/widgets/reader_settings_modal.dart
Normal file
425
lib/modules/manga/reader/widgets/reader_settings_modal.dart
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/misc.dart' show ProviderListenable;
|
||||
import 'package:mangayomi/models/settings.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/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
|
||||
/// Settings modal for the manga reader using Riverpod providers directly.
|
||||
///
|
||||
/// This is a complete replacement for the _showModalSettings() method.
|
||||
/// It uses the same providers and matches the exact behavior.
|
||||
class ReaderSettingsModal {
|
||||
/// Shows the settings modal.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [context]: The build context
|
||||
/// - [vsync]: The ticker provider (usually the State object)
|
||||
/// - [currentReaderModeProvider]: The provider for current reader mode
|
||||
/// - [autoScrollPage]: ValueNotifier for auto-scroll page state
|
||||
/// - [autoScroll]: ValueNotifier for auto-scroll running state
|
||||
/// - [pageOffset]: ValueNotifier for page offset (scroll speed)
|
||||
/// - [onReaderModeChanged]: Callback when reader mode changes
|
||||
/// - [onAutoScrollSave]: Callback to save auto-scroll settings
|
||||
/// - [onFullScreenToggle]: Callback to toggle fullscreen
|
||||
/// - [onAutoPageScroll]: Callback to trigger auto-scroll
|
||||
static Future<void> show({
|
||||
required BuildContext context,
|
||||
required TickerProvider vsync,
|
||||
required ProviderListenable<ReaderMode?> currentReaderModeProvider,
|
||||
required ValueNotifier<bool> autoScrollPage,
|
||||
required ValueNotifier<bool> autoScroll,
|
||||
required ValueNotifier<double> pageOffset,
|
||||
required void Function(ReaderMode mode, WidgetRef ref) onReaderModeChanged,
|
||||
required void Function(bool enabled, double offset) onAutoScrollSave,
|
||||
required VoidCallback onFullScreenToggle,
|
||||
required VoidCallback onAutoPageScroll,
|
||||
}) async {
|
||||
// Pause auto-scroll while settings are open
|
||||
final autoScrollWasRunning = autoScroll.value;
|
||||
if (autoScrollWasRunning) {
|
||||
autoScroll.value = false;
|
||||
}
|
||||
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
|
||||
await customDraggableTabBar(
|
||||
tabs: [
|
||||
Tab(text: l10n.reading_mode),
|
||||
Tab(text: l10n.general),
|
||||
Tab(text: l10n.custom_filter),
|
||||
],
|
||||
children: [
|
||||
// Reading Mode Tab
|
||||
_ReadingModeTab(
|
||||
currentReaderModeProvider: currentReaderModeProvider,
|
||||
autoScrollPage: autoScrollPage,
|
||||
pageOffset: pageOffset,
|
||||
onReaderModeChanged: onReaderModeChanged,
|
||||
onAutoScrollSave: onAutoScrollSave,
|
||||
onAutoScroll: (val) {
|
||||
autoScroll.value = val;
|
||||
},
|
||||
),
|
||||
|
||||
// General Tab
|
||||
_GeneralTab(onFullScreenToggle: onFullScreenToggle),
|
||||
|
||||
// Custom Filter Tab
|
||||
const _CustomFilterTab(),
|
||||
],
|
||||
context: context,
|
||||
vsync: vsync,
|
||||
fullWidth: true,
|
||||
);
|
||||
|
||||
// Resume auto-scroll if it was running
|
||||
if (autoScrollWasRunning || autoScroll.value) {
|
||||
if (autoScrollPage.value) {
|
||||
onAutoPageScroll();
|
||||
autoScroll.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reading Mode Tab with Consumer for reactive updates.
|
||||
class _ReadingModeTab extends ConsumerWidget {
|
||||
final ProviderListenable<ReaderMode?> currentReaderModeProvider;
|
||||
final ValueNotifier<bool> autoScrollPage;
|
||||
final ValueNotifier<double> pageOffset;
|
||||
final void Function(ReaderMode mode, WidgetRef ref) onReaderModeChanged;
|
||||
final void Function(bool enabled, double offset) onAutoScrollSave;
|
||||
final void Function(bool val) onAutoScroll;
|
||||
|
||||
const _ReadingModeTab({
|
||||
required this.currentReaderModeProvider,
|
||||
required this.autoScrollPage,
|
||||
required this.pageOffset,
|
||||
required this.onReaderModeChanged,
|
||||
required this.onAutoScrollSave,
|
||||
required this.onAutoScroll,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final readerMode = ref.watch(currentReaderModeProvider);
|
||||
final usePageTapZones = ref.watch(usePageTapZonesStateProvider);
|
||||
final cropBorders = ref.watch(cropBordersStateProvider);
|
||||
|
||||
final isContinuousMode =
|
||||
readerMode == ReaderMode.verticalContinuous ||
|
||||
readerMode == ReaderMode.webtoon ||
|
||||
readerMode == ReaderMode.horizontalContinuous;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
// Reader Mode
|
||||
CustomPopupMenuButton<ReaderMode>(
|
||||
label: l10n.reading_mode,
|
||||
title: getReaderModeName(readerMode!, context),
|
||||
onSelected: (value) {
|
||||
onReaderModeChanged(value, ref);
|
||||
},
|
||||
value: readerMode,
|
||||
list: ReaderMode.values,
|
||||
itemText: (mode) => getReaderModeName(mode, context),
|
||||
),
|
||||
|
||||
// Crop Borders
|
||||
SwitchListTile(
|
||||
value: cropBorders,
|
||||
title: Text(
|
||||
l10n.crop_borders,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref.read(cropBordersStateProvider.notifier).set(value);
|
||||
},
|
||||
),
|
||||
|
||||
// Page Tap Zones
|
||||
SwitchListTile(
|
||||
value: usePageTapZones,
|
||||
title: Text(
|
||||
l10n.use_page_tap_zones,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref.read(usePageTapZonesStateProvider.notifier).set(value);
|
||||
},
|
||||
),
|
||||
|
||||
// Auto-scroll (only for continuous modes)
|
||||
if (isContinuousMode)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: autoScrollPage,
|
||||
builder: (context, valueT, child) {
|
||||
return Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
secondary: Icon(
|
||||
valueT ? Icons.timer : Icons.timer_outlined,
|
||||
),
|
||||
value: valueT,
|
||||
title: Text(
|
||||
context.l10n.auto_scroll,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color!
|
||||
.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onChanged: (val) {
|
||||
onAutoScrollSave(val, pageOffset.value);
|
||||
autoScrollPage.value = val;
|
||||
onAutoScroll(val);
|
||||
},
|
||||
),
|
||||
if (valueT)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: pageOffset,
|
||||
builder: (context, value, child) => Slider(
|
||||
min: 2.0,
|
||||
max: 30.0,
|
||||
divisions: max(28, 3),
|
||||
value: value,
|
||||
onChanged: (val) {
|
||||
pageOffset.value = val;
|
||||
},
|
||||
onChangeEnd: (val) {
|
||||
onAutoScrollSave(valueT, val);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// General Tab with Consumer for reactive updates.
|
||||
class _GeneralTab extends ConsumerWidget {
|
||||
final VoidCallback onFullScreenToggle;
|
||||
|
||||
const _GeneralTab({required this.onFullScreenToggle});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final showPagesNumber = ref.watch(showPagesNumberStateProvider);
|
||||
final animatePageTransitions = ref.watch(
|
||||
animatePageTransitionsStateProvider,
|
||||
);
|
||||
final scaleType = ref.watch(scaleTypeStateProvider);
|
||||
final fullScreenReader = ref.watch(fullScreenReaderStateProvider);
|
||||
final backgroundColor = ref.watch(backgroundColorStateProvider);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Background Color
|
||||
CustomPopupMenuButton<BackgroundColor>(
|
||||
label: l10n.background_color,
|
||||
title: getBackgroundColorName(backgroundColor, context),
|
||||
onSelected: (value) {
|
||||
ref.read(backgroundColorStateProvider.notifier).set(value);
|
||||
},
|
||||
value: backgroundColor,
|
||||
list: BackgroundColor.values,
|
||||
itemText: (color) => getBackgroundColorName(color, context),
|
||||
),
|
||||
|
||||
// Scale Type
|
||||
CustomPopupMenuButton<ScaleType>(
|
||||
label: l10n.scale_type,
|
||||
title: getScaleTypeNames(context)[scaleType.index],
|
||||
onSelected: (value) {
|
||||
ref
|
||||
.read(scaleTypeStateProvider.notifier)
|
||||
.set(ScaleType.values[value.index]);
|
||||
},
|
||||
value: scaleType,
|
||||
list: ScaleType.values.where((scale) {
|
||||
try {
|
||||
return getScaleTypeNames(
|
||||
context,
|
||||
).contains(getScaleTypeNames(context)[scale.index]);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}).toList(),
|
||||
itemText: (scale) => getScaleTypeNames(context)[scale.index],
|
||||
),
|
||||
|
||||
// Fullscreen
|
||||
SwitchListTile(
|
||||
value: fullScreenReader,
|
||||
title: Text(
|
||||
l10n.fullscreen,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
onFullScreenToggle();
|
||||
},
|
||||
),
|
||||
|
||||
// Show Page Numbers
|
||||
SwitchListTile(
|
||||
value: showPagesNumber,
|
||||
title: Text(
|
||||
l10n.show_page_number,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref.read(showPagesNumberStateProvider.notifier).set(value);
|
||||
},
|
||||
),
|
||||
|
||||
// Animate Page Transitions
|
||||
SwitchListTile(
|
||||
value: animatePageTransitions,
|
||||
title: Text(
|
||||
l10n.animate_page_transitions,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(animatePageTransitionsStateProvider.notifier)
|
||||
.set(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom Filter Tab with Consumer for reactive updates.
|
||||
class _CustomFilterTab extends ConsumerWidget {
|
||||
const _CustomFilterTab();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final customColorFilter = ref.watch(customColorFilterStateProvider);
|
||||
final enableCustomColorFilter = ref.watch(
|
||||
enableCustomColorFilterStateProvider,
|
||||
);
|
||||
final colorFilterBlendMode = ref.watch(colorFilterBlendModeStateProvider);
|
||||
|
||||
int r = customColorFilter?.r ?? 0;
|
||||
int g = customColorFilter?.g ?? 0;
|
||||
int b = customColorFilter?.b ?? 0;
|
||||
int a = customColorFilter?.a ?? 0;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Enable Custom Color Filter
|
||||
SwitchListTile(
|
||||
value: enableCustomColorFilter,
|
||||
title: Text(
|
||||
l10n.custom_color_filter,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color!.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(enableCustomColorFilterStateProvider.notifier)
|
||||
.set(value);
|
||||
},
|
||||
),
|
||||
|
||||
if (enableCustomColorFilter) ...[
|
||||
// RGBA Sliders
|
||||
rgbaFilterWidget(a, r, g, b, (val) {
|
||||
final notifier = ref.read(
|
||||
customColorFilterStateProvider.notifier,
|
||||
);
|
||||
if (val.$3 == "r") {
|
||||
notifier.set(a, val.$1.toInt(), g, b, val.$2);
|
||||
} else if (val.$3 == "g") {
|
||||
notifier.set(a, r, val.$1.toInt(), b, val.$2);
|
||||
} else if (val.$3 == "b") {
|
||||
notifier.set(a, r, g, val.$1.toInt(), val.$2);
|
||||
} else {
|
||||
notifier.set(val.$1.toInt(), r, g, b, val.$2);
|
||||
}
|
||||
}, context),
|
||||
|
||||
// Blend Mode
|
||||
CustomPopupMenuButton<ColorFilterBlendMode>(
|
||||
label: l10n.color_filter_blend_mode,
|
||||
title: getColorFilterBlendModeName(
|
||||
colorFilterBlendMode,
|
||||
context,
|
||||
),
|
||||
onSelected: (value) {
|
||||
ref
|
||||
.read(colorFilterBlendModeStateProvider.notifier)
|
||||
.set(value);
|
||||
},
|
||||
value: colorFilterBlendMode,
|
||||
list: ColorFilterBlendMode.values,
|
||||
itemText: (mode) => getColorFilterBlendModeName(mode, context),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,11 @@ import 'package:mangayomi/main.dart';
|
|||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/more/about/providers/check_for_update.dart';
|
||||
import 'package:mangayomi/modules/more/about/providers/get_package_info.dart';
|
||||
import 'package:mangayomi/modules/more/about/providers/logs_state.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/utils/log/logger.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
|
@ -23,6 +25,7 @@ class AboutScreen extends ConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = l10nLocalizations(context);
|
||||
final checkForUpdates = ref.watch(checkForAppUpdatesProvider);
|
||||
final enableLogs = ref.watch(logsStateProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n!.about)),
|
||||
body: ref
|
||||
|
|
@ -80,38 +83,58 @@ class AboutScreen extends ConsumerWidget {
|
|||
},
|
||||
title: Text(l10n.check_for_update),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
final storage = StorageProvider();
|
||||
final directory = await storage.getDefaultDirectory();
|
||||
final file = File(
|
||||
path.join(directory!.path, 'logs.txt'),
|
||||
);
|
||||
if (await file.exists()) {
|
||||
if (Platform.isLinux) {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: file.path),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [XFile(file.path)],
|
||||
text: "log.txt",
|
||||
sharePositionOrigin:
|
||||
box!.localToGlobal(Offset.zero) &
|
||||
box.size,
|
||||
),
|
||||
);
|
||||
}
|
||||
SwitchListTile(
|
||||
title: Text(l10n.logs_on),
|
||||
value: enableLogs,
|
||||
onChanged: (value) {
|
||||
isar.writeTxnSync(() {
|
||||
final settings = isar.settings.getSync(227);
|
||||
isar.settings.putSync(
|
||||
settings!..enableLogs = value,
|
||||
);
|
||||
});
|
||||
ref.invalidate(logsStateProvider);
|
||||
if (value) {
|
||||
AppLogger.init();
|
||||
} else {
|
||||
botToast(l10n.no_app_logs);
|
||||
AppLogger.dispose();
|
||||
}
|
||||
},
|
||||
title: Text(l10n.share_app_logs),
|
||||
),
|
||||
if (enableLogs)
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
final storage = StorageProvider();
|
||||
final directory = await storage
|
||||
.getDefaultDirectory();
|
||||
final file = File(
|
||||
path.join(directory!.path, 'logs.txt'),
|
||||
);
|
||||
if (await file.exists()) {
|
||||
if (Platform.isLinux) {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: file.path),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [XFile(file.path)],
|
||||
text: "log.txt",
|
||||
sharePositionOrigin:
|
||||
box!.localToGlobal(Offset.zero) &
|
||||
box.size,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
botToast(l10n.no_app_logs);
|
||||
}
|
||||
},
|
||||
title: Text(l10n.share_app_logs),
|
||||
),
|
||||
// ListTile(
|
||||
// onTap: () {},
|
||||
// title: const Text("What's news"),
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ part of 'check_for_update.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(checkForUpdate)
|
||||
const checkForUpdateProvider = CheckForUpdateFamily._();
|
||||
final checkForUpdateProvider = CheckForUpdateFamily._();
|
||||
|
||||
final class CheckForUpdateProvider
|
||||
extends $FunctionalProvider<AsyncValue<void>, void, FutureOr<void>>
|
||||
with $FutureModifier<void>, $FutureProvider<void> {
|
||||
const CheckForUpdateProvider._({
|
||||
CheckForUpdateProvider._({
|
||||
required CheckForUpdateFamily super.from,
|
||||
required ({BuildContext? context, bool? manualUpdate}) super.argument,
|
||||
}) : super(
|
||||
|
|
@ -71,7 +71,7 @@ final class CheckForUpdateFamily extends $Family
|
|||
FutureOr<void>,
|
||||
({BuildContext? context, bool? manualUpdate})
|
||||
> {
|
||||
const CheckForUpdateFamily._()
|
||||
CheckForUpdateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'checkForUpdateProvider',
|
||||
|
|
@ -91,12 +91,12 @@ final class CheckForUpdateFamily extends $Family
|
|||
}
|
||||
|
||||
@ProviderFor(checkForAppUpdates)
|
||||
const checkForAppUpdatesProvider = CheckForAppUpdatesProvider._();
|
||||
final checkForAppUpdatesProvider = CheckForAppUpdatesProvider._();
|
||||
|
||||
final class CheckForAppUpdatesProvider
|
||||
extends $FunctionalProvider<bool, bool, bool>
|
||||
with $Provider<bool> {
|
||||
const CheckForAppUpdatesProvider._()
|
||||
CheckForAppUpdatesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ part of 'get_package_info.dart';
|
|||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(getPackageInfo)
|
||||
const getPackageInfoProvider = GetPackageInfoProvider._();
|
||||
final getPackageInfoProvider = GetPackageInfoProvider._();
|
||||
|
||||
final class GetPackageInfoProvider
|
||||
extends
|
||||
|
|
@ -20,7 +20,7 @@ final class GetPackageInfoProvider
|
|||
FutureOr<PackageInfo>
|
||||
>
|
||||
with $FutureModifier<PackageInfo>, $FutureProvider<PackageInfo> {
|
||||
const GetPackageInfoProvider._()
|
||||
GetPackageInfoProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
|
|
|
|||
9
lib/modules/more/about/providers/logs_state.dart
Normal file
9
lib/modules/more/about/providers/logs_state.dart
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'logs_state.g.dart';
|
||||
|
||||
@riverpod
|
||||
bool logsState(Ref ref) {
|
||||
return isar.settings.getSync(227)?.enableLogs ?? false;
|
||||
}
|
||||
50
lib/modules/more/about/providers/logs_state.g.dart
Normal file
50
lib/modules/more/about/providers/logs_state.g.dart
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'logs_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(logsState)
|
||||
final logsStateProvider = LogsStateProvider._();
|
||||
|
||||
final class LogsStateProvider extends $FunctionalProvider<bool, bool, bool>
|
||||
with $Provider<bool> {
|
||||
LogsStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'logsStateProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$logsStateHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<bool> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
bool create(Ref ref) {
|
||||
return logsState(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(bool value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<bool>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$logsStateHash() => r'680ab781a039e0441394dc0b376b8add0fb80910';
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar_community/isar.dart';
|
||||
|
|
@ -11,6 +13,8 @@ import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_pr
|
|||
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/item_type_filters.dart';
|
||||
import 'package:mangayomi/utils/item_type_localization.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
class CategoriesScreen extends ConsumerStatefulWidget {
|
||||
|
|
@ -24,17 +28,15 @@ class CategoriesScreen extends ConsumerStatefulWidget {
|
|||
class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
|
||||
with TickerProviderStateMixin {
|
||||
late TabController _tabBarController;
|
||||
late final List<String> _tabList;
|
||||
late final List<ItemType> _visibleTabTypes;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final hideItems = ref.read(hideItemsStateProvider);
|
||||
_tabList = [
|
||||
if (!hideItems.contains("/MangaLibrary")) "/MangaLibrary",
|
||||
if (!hideItems.contains("/AnimeLibrary")) "/AnimeLibrary",
|
||||
if (!hideItems.contains("/NovelLibrary")) "/NovelLibrary",
|
||||
];
|
||||
_tabBarController = TabController(length: _tabList.length, vsync: this);
|
||||
_visibleTabTypes = hiddenItemTypes(ref.read(hideItemsStateProvider));
|
||||
_tabBarController = TabController(
|
||||
length: _visibleTabTypes.length,
|
||||
vsync: this,
|
||||
);
|
||||
_tabBarController.animateTo(widget.data.$2);
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +48,7 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_tabList.isEmpty) {
|
||||
if (_visibleTabTypes.isEmpty) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(context.l10n.categories)),
|
||||
body: Center(child: Text("EMPTY\nMPTY\nMTY\nMT\n\n")),
|
||||
|
|
@ -55,7 +57,7 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
|
|||
final l10n = l10nLocalizations(context)!;
|
||||
return DefaultTabController(
|
||||
animationDuration: Duration.zero,
|
||||
length: _tabList.length,
|
||||
length: _visibleTabTypes.length,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
|
|
@ -67,23 +69,15 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
|
|||
bottom: TabBar(
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
controller: _tabBarController,
|
||||
tabs: _tabList.map((route) {
|
||||
if (route == "/MangaLibrary") return Tab(text: l10n.manga);
|
||||
if (route == "/AnimeLibrary") return Tab(text: l10n.anime);
|
||||
return Tab(text: l10n.novel);
|
||||
tabs: _visibleTabTypes.map((type) {
|
||||
return Tab(text: type.localized(l10n));
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabBarController,
|
||||
children: _tabList.map((route) {
|
||||
if (route == "/MangaLibrary") {
|
||||
return CategoriesTab(itemType: ItemType.manga);
|
||||
}
|
||||
if (route == "/AnimeLibrary") {
|
||||
return CategoriesTab(itemType: ItemType.anime);
|
||||
}
|
||||
return CategoriesTab(itemType: ItemType.novel);
|
||||
children: _visibleTabTypes.map((type) {
|
||||
return CategoriesTab(itemType: type);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
|
|
@ -99,17 +93,75 @@ class CategoriesTab extends ConsumerStatefulWidget {
|
|||
ConsumerState<CategoriesTab> createState() => _CategoriesTabState();
|
||||
}
|
||||
|
||||
class _CategoriesTabState extends ConsumerState<CategoriesTab> {
|
||||
class _CategoriesTabState extends ConsumerState<CategoriesTab>
|
||||
with SingleTickerProviderStateMixin {
|
||||
List<Category> _entries = [];
|
||||
void _updateCategoriesOrder(List<Category> categories) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.categorys.clearSync();
|
||||
isar.categorys.putAllSync(categories);
|
||||
final cats = isar.categorys.filter().posIsNull().findAllSync();
|
||||
for (var category in cats) {
|
||||
isar.categorys.putSync(category..pos = category.id);
|
||||
}
|
||||
});
|
||||
late AnimationController _swapAnimationController;
|
||||
int? _animatingFromIndex;
|
||||
int? _animatingToIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_swapAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_swapAnimationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get _isDesktop {
|
||||
if (kIsWeb) return false;
|
||||
return Platform.isMacOS || Platform.isLinux || Platform.isWindows;
|
||||
}
|
||||
|
||||
/// Moves a category from `index` to `newIndex` in the list,
|
||||
/// swaps their positions in memory, and persists the change in Isar.
|
||||
Future<void> _moveCategory(int index, int newIndex) async {
|
||||
// Prevent invalid moves (out of bounds)
|
||||
if (newIndex < 0 || newIndex >= _entries.length) return;
|
||||
|
||||
if (_isDesktop && mounted) {
|
||||
setState(() {
|
||||
_animatingFromIndex = index;
|
||||
_animatingToIndex = newIndex;
|
||||
});
|
||||
|
||||
await _swapAnimationController.forward(from: 0.0);
|
||||
|
||||
// Grab the two category objects involved in the swap
|
||||
final a = _entries[index];
|
||||
final b = _entries[newIndex];
|
||||
// Swap their positions inside the in‑memory list
|
||||
_entries[newIndex] = a;
|
||||
_entries[index] = b;
|
||||
// Swap their persisted `pos` values so ordering is saved correctly
|
||||
final temp = a.pos;
|
||||
a.pos = b.pos;
|
||||
b.pos = temp;
|
||||
// Persist both updated objects in a single Isar transaction
|
||||
await isar.writeTxn(() async => isar.categorys.putAll([a, b]));
|
||||
|
||||
setState(() {
|
||||
_animatingFromIndex = null;
|
||||
_animatingToIndex = null;
|
||||
});
|
||||
} else {
|
||||
final a = _entries[index];
|
||||
final b = _entries[newIndex];
|
||||
_entries[newIndex] = a;
|
||||
_entries[index] = b;
|
||||
final temp = a.pos;
|
||||
a.pos = b.pos;
|
||||
b.pos = temp;
|
||||
await isar.writeTxn(() async => isar.categorys.putAll([a, b]));
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -141,196 +193,37 @@ class _CategoriesTabState extends ConsumerState<CategoriesTab> {
|
|||
padding: const EdgeInsets.only(bottom: 100),
|
||||
itemBuilder: (context, index) {
|
||||
final category = _entries[index];
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 900),
|
||||
child: Padding(
|
||||
key: Key('category_${category.id}'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Card(
|
||||
child: Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
shadowColor: Colors.transparent,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(0),
|
||||
bottomRight: Radius.circular(0),
|
||||
topRight: Radius.circular(10),
|
||||
topLeft: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
_renameCategory(category);
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
const Icon(Icons.label_outline_rounded),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(category.name!)),
|
||||
],
|
||||
),
|
||||
|
||||
Widget itemWidget = _buildCategoryCard(context, category, index);
|
||||
|
||||
if (_isDesktop &&
|
||||
_animatingFromIndex != null &&
|
||||
_animatingToIndex != null) {
|
||||
if (index == _animatingFromIndex ||
|
||||
index == _animatingToIndex) {
|
||||
final isMovingDown =
|
||||
_animatingFromIndex! < _animatingToIndex!;
|
||||
final offset = index == _animatingFromIndex
|
||||
? (isMovingDown ? 1.0 : -1.0)
|
||||
: (isMovingDown ? -1.0 : 1.0);
|
||||
|
||||
itemWidget = AnimatedBuilder(
|
||||
animation: _swapAnimationController,
|
||||
builder: (context, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(
|
||||
0,
|
||||
offset * (1 - _swapAnimationController.value) * 80,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_drop_up_outlined,
|
||||
),
|
||||
onPressed: index > 0
|
||||
? () {
|
||||
final item = _entries[index - 1];
|
||||
_entries.removeAt(index);
|
||||
_entries.removeAt(index - 1);
|
||||
int? currentPos = category.pos;
|
||||
int? pos = item.pos;
|
||||
setState(() {});
|
||||
_updateCategoriesOrder([
|
||||
..._entries,
|
||||
category..pos = pos,
|
||||
item..pos = currentPos,
|
||||
]);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_drop_down_outlined,
|
||||
),
|
||||
onPressed: index < _entries.length - 1
|
||||
? () {
|
||||
final item = _entries[index + 1];
|
||||
_entries.removeAt(index + 1);
|
||||
_entries.removeAt(index);
|
||||
int? currentPos = category.pos;
|
||||
int? pos = item.pos;
|
||||
setState(() {});
|
||||
_updateCategoriesOrder([
|
||||
..._entries,
|
||||
category..pos = pos,
|
||||
item..pos = currentPos,
|
||||
]);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_renameCategory(category);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.mode_edit_outline_outlined,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
isar.writeTxnSync(() async {
|
||||
category.hide = !(category.hide ?? false);
|
||||
category.updatedAt =
|
||||
DateTime.now().millisecondsSinceEpoch;
|
||||
isar.categorys.putSync(category);
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
!(category.hide ?? false)
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.delete_category),
|
||||
content: Text(
|
||||
l10n.delete_category_msg(
|
||||
category.name!,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await _removeCategory(
|
||||
category,
|
||||
context,
|
||||
);
|
||||
},
|
||||
child: Text(l10n.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return SlideTransition(
|
||||
position:
|
||||
Tween<Offset>(
|
||||
begin: const Offset(0, 1),
|
||||
end: Offset.zero,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
),
|
||||
),
|
||||
child: SizeTransition(
|
||||
sizeFactor: CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
),
|
||||
axisAlignment: 0.5,
|
||||
child: child,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: itemWidget,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return itemWidget;
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
@ -446,6 +339,150 @@ class _CategoriesTabState extends ConsumerState<CategoriesTab> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildCategoryCard(
|
||||
BuildContext context,
|
||||
Category category,
|
||||
int index,
|
||||
) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return Padding(
|
||||
key: Key('category_${category.id}'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Card(
|
||||
child: Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
shadowColor: Colors.transparent,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(0),
|
||||
bottomRight: Radius.circular(0),
|
||||
topRight: Radius.circular(10),
|
||||
topLeft: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
_renameCategory(category);
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
const Icon(Icons.label_outline_rounded),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(category.name!)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_drop_up_outlined),
|
||||
onPressed: index > 0
|
||||
? () {
|
||||
_moveCategory(index, index - 1);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_drop_down_outlined),
|
||||
onPressed: index < _entries.length - 1
|
||||
? () {
|
||||
_moveCategory(index, index + 1);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_renameCategory(category);
|
||||
},
|
||||
icon: const Icon(Icons.mode_edit_outline_outlined),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await isar.writeTxn(() async {
|
||||
category.hide = !(category.hide ?? false);
|
||||
category.updatedAt =
|
||||
DateTime.now().millisecondsSinceEpoch;
|
||||
isar.categorys.put(category);
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
!(category.hide ?? false)
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.delete_category),
|
||||
content: Text(
|
||||
l10n.delete_category_msg(category.name!),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await _removeCategory(
|
||||
category,
|
||||
context,
|
||||
);
|
||||
},
|
||||
child: Text(l10n.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _removeCategory(Category category, BuildContext context) async {
|
||||
await isar.writeTxn(() async {
|
||||
// All Items with this category
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue