Compare commits

...

88 commits

Author SHA1 Message Date
GitHub Action
89dd50dc25 source update: v0.7.0 2026-01-09 11:15:37 +00:00
Moustapha Kodjo Amadou
1f3fe65945 v0.7.0 2026-01-09 11:55:13 +01:00
Moustapha Kodjo Amadou
88f8d7b4be - 2026-01-09 11:54:10 +01:00
Moustapha Kodjo Amadou
83b7d31e0a Increase isolate pool size to enhance concurrent download capabilities 2026-01-09 11:48:23 +01:00
Moustapha Kodjo Amadou
40fdfc2592 v0.6.95 2026-01-08 14:36:38 +01:00
Moustapha Kodjo Amadou
74b194602c update dependencies & update riverpod provider declarations 2026-01-08 14:27:08 +01:00
Moustapha Kodjo Amadou
b9f9a8398f Refactor download management with shared isolate pool and optimize concurrent downloads 2026-01-08 13:01:15 +01:00
Moustapha Kodjo Amadou
9458ae120b fix #637 2026-01-07 13:07:55 +01:00
Moustapha Kodjo Amadou
701a696820 Enhance category management with animation 2026-01-05 12:38:30 +01:00
Moustapha Kodjo Amadou
db24951673 Add localization 2026-01-05 12:15:14 +01:00
Moustapha Kodjo Amadou
7c4c8d8a20 + 2026-01-05 12:12:56 +01:00
Moustapha Kodjo Amadou
6b70fff6cc
Merge pull request #638 from 686udjie/main
add a force landscapre mode on mobile
2026-01-05 12:11:33 +01:00
Moustapha Kodjo Amadou
2365e28a1a
Merge pull request #636 from NBA2K1/main
Fix "Show extensions" Button AND Fix category rearrange bug AND hide ItemType from calendar
2026-01-05 11:55:33 +01:00
NBA2K1
922582b270 Fix share params to avoid Text file
- avoids extra text file
- simplify RenderBox lookup
2026-01-04 20:25:13 +01:00
686udjie
79ee4b4fc8 add a force landscapre mode on mobile 2026-01-01 19:48:57 +02:00
NBA2K1
c8328fa347 improve performance of helper method
- compare integers instead of Strings
- cache results per day
2025-12-30 02:44:45 +01:00
NBA2K1
a63f0d67bd Extract helper methods
for better readability
2025-12-30 02:41:47 +01:00
NBA2K1
2c92d74a03 Fix invalid selection
Don't show manga calendar if manga is hidden.
2025-12-30 02:04:06 +01:00
NBA2K1
a2a10a799d Hide items in Calendar
When the user has hidden Manga or Anime or Novel, hide the buttons in the calendar.
2025-12-30 01:37:30 +01:00
NBA2K1
ba77c5baea Reduce Code Duplication
And add comments
2025-12-30 01:15:36 +01:00
NBA2K1
4e2d8b0038 Fix category rearrange bug
When rearranging categories in the Manga library, all categories in Anime and Novel were being deleted.
2025-12-30 00:27:49 +01:00
NBA2K1
821cbfa0dd Make function async
_updateCategoriesOrder() is now async
2025-12-29 23:38:17 +01:00
NBA2K1
4fb9f0a9df remove redundant things 2025-12-29 23:20:08 +01:00
NBA2K1
1ac605e30a Use the localized extension 2025-12-29 23:20:08 +01:00
NBA2K1
9efd76581f Group related functions using an extension
- new file item_type_filters.dart for the hiddenItemTypes function.
- reduces code duplication in statistics_screen, categories_screen and base_library_tab_screen
2025-12-29 23:19:31 +01:00
NBA2K1
7664e38cfd Fix "Show extensions" Button
The "Show extensions" Button was not working properly when categories were hidden.
An Exception would throw:
```
_AssertionError ('package:flutter/src/material/tab_controller.dart': Failed assertion: line 202 pos 12: 'value >= 0 && (value < length || length == 0)': is not true.)
```
And jump to the end of the tab list regardless of where you clicked the button.
2025-12-29 05:02:43 +01:00
NBA2K1
42f1dcff92 Reduce Code Duplication
- Extract localizedItemType() from `base_library_tab_screen.dart`
- Add localizedSources() and localizedExtensions() for browse_screen.dart
- Reduce if-statements in statistics_screen.dart
- Reduce if-statements in categories_screen.dart
- Reduce if-statements in browse_screen.dart
2025-12-29 04:59:06 +01:00
NBA2K1
86fb19ecb2 Refactor tabbed screens into base class
Reduce duplication
2025-12-29 03:28:04 +01:00
NBA2K1
bdcd28488e Same with history_screen.dart
- remove hideItems parameter
- Make TabBar and TabBarView cleaner
2025-12-29 01:58:49 +01:00
NBA2K1
30e6d50210 Make TabBar and TabBarView cleaner
Use the _visibleTabTypes list instead of the repeated if-statements.
2025-12-29 01:34:05 +01:00
NBA2K1
66b508d65d Cleanup
- Change _tabList String-list to be a list of ItemType
- Change its name to _visibleTabTypes
- Use that list in getCurrentItemType(), instead of creating a new list
2025-12-29 01:13:26 +01:00
NBA2K1
004885d557 Return library updater if library empty 2025-12-29 01:00:01 +01:00
NBA2K1
e463cce61a Make getCurrentItemType() readable
The previous version was just unnecessarily complicated, hard to read.
New version is very easy to read.
2025-12-29 00:38:33 +01:00
NBA2K1
67a83c0e6a Cleanup
Use getCurrentItemType() method to determine the ItemType of the current tab instead of the conditional with `_tabBarController.index`, which doesn't take into account that there could be hidden tabs.
2025-12-28 23:42:45 +01:00
NBA2K1
da566d3d0b remove hideItems parameter
No need to pass hideItems down, they all can use the top hideItems variable. It's never changed.
2025-12-28 23:16:29 +01:00
NBA2K1
c911594e73 Add logging and detailed botToast, remove doWhile
- Use the logger to log failed updates

- After the update-botToast another botToast is being spawned to show exactly which manga(s) couldn't be updated.

- Remove the doWhile loop because it is unnecessary. The condition of mangaList.length == numbers is always true, meaning it only runs once.
2025-12-28 23:08:54 +01:00
NBA2K1
9bd8a62d31 Reduce Code Duplication
Extract the `_updateLibrary()` function from `updates_screen.dart` and `library_screen.dart` to a top-level function in `libray_updater.dart`.
2025-12-28 22:59:44 +01:00
GitHub Action
53cd2101f4 source update: v0.6.90 2025-12-28 03:21:33 +00:00
Moustapha Kodjo Amadou
4e18e18489 Update 2025-12-24 20:26:51 +01:00
Moustapha Kodjo Amadou
481deb1344 v0.6.90 2025-12-23 19:20:00 +01:00
Moustapha Kodjo Amadou
344533aeb1 fix: show chanded source preference for mihon source 2025-12-23 19:18:18 +01:00
Moustapha Kodjo Amadou
e40e1b8fe6 fix: improve handling of proxy server URL in dialog 2025-12-23 18:32:04 +01:00
Moustapha Kodjo Amadou
b5d37caaa9 chore: update d4rt dependency version 2025-12-23 18:25:53 +01:00
Moustapha Kodjo Amadou
e342fe16fb
Merge pull request #632 from NBA2K1/path-optimizations
Path optimizations
2025-12-18 17:09:33 +01:00
Moustapha Kodjo Amadou
390e6fed46
Merge pull request #629 from NBA2K1/main
Add log switch to about-menu
2025-12-18 17:05:01 +01:00
Moustapha Kodjo Amadou
19a051b660 feat: add updatePageCropImage method for chapter page cropping 2025-12-18 17:03:06 +01:00
NBA2K1
5f7ea7fcf4 reduce code duplication
Use the `formattedFileSize()` function instead of `_formatBytes()`
2025-12-17 22:25:45 +01:00
NBA2K1
0f83899bac remove unnecessary calls
.trimLeft().trimRight() after trim() is unnecessary.
2025-12-17 21:34:45 +01:00
NBA2K1
a078b59678 Use floor() instead of round()
- Using round() will push values up too early.
Example: log(999) / log(1024) = 0.999
.round() = 1.
Result: 0.98 kB instead of 999 B

- Use correct units, as base1024 = KiB, MiB, etc. and base1000 = kB, MB, GB, ...
2025-12-17 21:24:26 +01:00
NBA2K1
0ed8ee2cd2 Move cacheDir creation to storage_provider
- Move the cacheDir creation to storage_provider from `others.dart`, `custom_extended_image_provider.dart` and `storage_usage.dart`.

- Use the correct directory, `getApplicationCacheDirectory()` instead of the `getTemporaryDirectory()` (which is being deleted by the OS regularly) for cache files.

- remove the `_cacheDownloadPath` from `storage_usage.dart` as the path is never being created in the first place, so using that path in `clearCache()` and `_getTotalDiskSpace()` is unnecessary.
2025-12-17 20:55:41 +01:00
NBA2K1
67dee18776 remove redundant import 2025-12-17 20:43:23 +01:00
Moustapha Kodjo Amadou
76645d97c1 fix: improve error handling for Cloudflare bypass failures 2025-12-14 14:01:40 +01:00
Moustapha Kodjo Amadou
8fe910900b + 2025-12-14 13:17:47 +01:00
Moustapha Kodjo Amadou
1e469614d9 Chapter jump fix in vertical continuous mode 2025-12-14 13:16:19 +01:00
NBA2K1
284fccd1ef Add log-switch to app.
- New switch in More > Settings > About
"Enable logging".

When ON, it inits the AppLogger and shows the "Share app logs" button below.
When OFF, it disposes the AppLogger and hides the "Share app logs" button.
It also prevents the AppLogger from init.

OFF by default.

- added localizations accordingly.
2025-12-13 00:42:50 +01:00
GitHub Action
9ac6237caf source update: v0.6.85 2025-12-10 13:33:52 +00:00
Moustapha Kodjo Amadou
0bfcdaddf4 v0.6.85 2025-12-10 14:15:22 +01:00
Moustapha Kodjo Amadou
fc49b33826 feat(user-agent): add default user agent setting 2025-12-10 14:14:51 +01:00
Moustapha Kodjo Amadou
23e41373dc conditionally show Android proxy server option and update notes 2025-12-10 13:42:34 +01:00
Moustapha Kodjo Amadou
6e4d3dd52e feat(server): add server check before starting 2025-12-10 13:38:15 +01:00
Moustapha Kodjo Amadou
2cade3db56
Merge pull request #624 from NBA2K1/main
AppImage Fix
2025-12-10 13:30:41 +01:00
Moustapha Kodjo Amadou
1b708d6884 feat(server): integrate MExtensionServer 2025-12-08 15:14:12 +01:00
NBA2K1
77357312a0 AppImage Fix 2025-12-06 19:36:00 +01:00
Moustapha Kodjo Amadou
85ff4d7d4c Update 2025-12-06 14:00:10 +01:00
Moustapha Kodjo Amadou
da7c32f71e
Merge pull request #614 from hndrbrm/add_always_on_top_on_anime_player_view
Add new feature: Always On Top on the anime player view.
2025-12-05 17:15:05 +01:00
Moustapha Kodjo Amadou
e48c475fcb refactor(statistics): update statistics provider to use functional provider pattern 2025-12-05 17:03:51 +01:00
Moustapha Kodjo Amadou
4e9af30e8e feat(reader): add page indicator, app bar, bottom bar, gesture handler, and settings modal
- PageIndicator widget to display current page and total pages.
- Created ReaderAppBar for navigation and chapter information.
- ReaderBottomBar for page navigation and settings access.
- Added ReaderGestureHandler for managing tap zones and gestures.
- ReaderSettingsModal for user-configurable settings.
2025-12-05 16:54:10 +01:00
Moustapha Kodjo Amadou
0789f4c85a
Merge pull request #618 from Whiskas101/fix/statistics-synchronous-load
fix: updated statistics to use async fetch from db
2025-12-05 10:19:01 +01:00
Moustapha Kodjo Amadou
3f065feeef
Merge pull request #619 from Schnitzel5/fix/media-kit
fix media-kit crash
2025-12-01 09:58:05 +01:00
Schnitzel5
23ff95afce try media-kit fix 2025-11-30 18:55:16 +01:00
Whiskas101
04267b7a50 fix: updated statistics to use async fetch from db 2025-11-30 14:37:48 +05:30
GitHub Action
782d3963c1 source update: v0.6.80 2025-11-29 17:55:06 +00:00
Moustapha Kodjo Amadou
29091e4b5f v0.6.80 2025-11-29 18:27:37 +01:00
Moustapha Kodjo Amadou
a306b10e5e update 2025-11-29 18:27:20 +01:00
Moustapha Kodjo Amadou
3a1c69ef3f update issue template 2025-11-29 15:21:37 +01:00
hndrbrm
91b8c08658 Make the AlwaysOnTopStateMixin private for least scope principal. 2025-11-29 17:36:06 +07:00
hndrbrm
a6df770275 Add new feature: Always On Top on the anime player view. 2025-11-29 17:11:14 +07:00
Moustapha Kodjo Amadou
9b5bae831e fix #613 2025-11-29 06:38:45 +01:00
Moustapha Kodjo Amadou
958e91ac9a
Merge pull request #612 from NBA2K1/main
Various fixes
2025-11-29 06:29:15 +01:00
Moustapha Kodjo Amadou
6e5322137e
Merge pull request #610 from hndrbrm/main
Fix: Reverse icon for toggling the Extension.
2025-11-29 06:22:34 +01:00
NBA2K1
30c74423ad dispose in correct order 2025-11-29 02:57:25 +01:00
NBA2K1
c7e648a6d9 remove redundant calls 2025-11-29 01:36:34 +01:00
NBA2K1
33152fc035 Fix ProviderDisposedException
[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: Cannot use the Ref of totalChapterCacheSizeStateProvider after it has been disposed. This typically happens if:
- A provider rebuilt, but the previous "build" was still pending and is still performing operations.
  You should therefore either use `ref.onDispose` to cancel pending work, or
  check `ref.mounted` after async gaps or anything that could invalidate the provider.
- You tried to use Ref inside `onDispose` or other life-cycles.
  This is not supported, as the provider is already being disposed.

#0      Ref._throwIfInvalidUsage (package:riverpod/src/core/ref.dart:220:7)
ref.dart:220
#1      AnyNotifier.state= (package:riverpod/src/core/provider/notifier_provider.dart:91:9)
notifier_provider.dart:91
#2      TotalChapterCacheSizeState.build.<anonymous closure> (package:mangayomi/modules/more/data_and_storage/providers/storage_usage.dart:17:42)
storage_usage.dart:17
#3      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:948:45)
future_impl.dart:948
#4      Future._propagateToListeners (dart:async/future_impl.dart:977:13)
future_impl.dart:977
#5      Future._completeWithValue (dart:async/future_impl.dart:720:5)
future_impl.dart:720
<asynchronous suspension>
2025-11-29 01:31:22 +01:00
NBA2K1
5fdbf530cb stopCfResolutionWebviewServer()
Stop the cf server on app exit
2025-11-29 00:18:13 +01:00
NBA2K1
5c34dcab9a Start Cloudflare resolution after app starts
- Defer the Cloudflare resolution webserver until after runApp().

- await said function
2025-11-28 23:59:37 +01:00
hndrbrm
cc84c33c25 Fix: Reverse icon for toggling the Extension. 2025-11-29 05:41:54 +07:00
NBA2K1
e0ecc94869 remove redundant calls
trimLeft() and trimRight() after trim() do nothing
2025-11-28 23:33:32 +01:00
GitHub Action
01b3ed9f24 source update: v0.6.75 2025-11-28 15:48:14 +00:00
190 changed files with 7151 additions and 4912 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -305,6 +305,7 @@
"default_subtitle_language": "ডিফল্ট উপশিৰোনাম ভাষা",
"follow_system_theme": "ছিষ্টেম থিম অনুসৰণ কৰক",
"concurrent_downloads": "সমসাময়িক ডাউনলোড",
"logs_on": "লগিং সক্ষম কৰক",
"share_app_logs": "এপ লগ শ্বেয়াৰ কৰক",
"no_app_logs": "কোনো log.txt ফাইল উপলব্ধ নাই!",
"failed": "বিফল!",

View file

@ -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!",

View file

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

View file

@ -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!",

View file

@ -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!",

View file

@ -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é !",

View file

@ -305,6 +305,7 @@
"default_subtitle_language": "डिफ़ॉल्ट उपशीर्षक भाषा",
"follow_system_theme": "सिस्टम थीम का पालन करें",
"concurrent_downloads": "समवर्ती डाउनलोड",
"logs_on": "लॉगिंग सक्षम करें",
"share_app_logs": "ऐप लॉग साझा करें",
"no_app_logs": "कोई log.txt फ़ाइल उपलब्ध नहीं!",
"failed": "विफल!",

View file

@ -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!",

View file

@ -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!",

View file

@ -304,6 +304,7 @@
"default_subtitle_language": "デフォルト字幕言語",
"follow_system_theme": "システムテーマに従う",
"concurrent_downloads": "同時ダウンロード",
"logs_on": "ログを有効にする",
"share_app_logs": "アプリログを共有",
"no_app_logs": "log.txtファイルが利用できません",
"failed": "失敗!",

View file

@ -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!",

View file

@ -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!",

View file

@ -449,6 +449,7 @@
"downloaded_only": "Только загруженные",
"downloaded_only_description": "Показывать только загруженные записи в вашей библиотеке",
"concurrent_downloads": "Одновременные загрузки",
"logs_on": "Включить ведение журнала",
"share_app_logs": "Поделиться журналами приложения",
"no_app_logs": "Файл log.txt недоступен!",
"failed": "Не удалось!",

View file

@ -452,6 +452,7 @@
"downloaded_only": "ที่ดาวน์โหลดแล้วเท่านั้น",
"downloaded_only_description": "แสดงเฉพาะรายการที่ดาวน์โหลดแล้วในห้องสมุดของคุณ",
"concurrent_downloads": "ดาวน์โหลดพร้อมกัน",
"logs_on": "เปิดการบันทึก",
"share_app_logs": "แชร์บันทึกแอป",
"no_app_logs": "ไม่มีไฟล์ log.txt!",
"failed": "ล้มเหลว!",

View file

@ -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!",

View file

@ -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": "强制播放器使用横屏方向。"
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 => '强制播放器使用横屏方向。';
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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',

View file

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

View file

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

View file

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

View file

@ -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',

View file

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

View file

@ -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',

View file

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

View file

@ -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',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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',

View file

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

View file

@ -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',

View file

@ -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',

View file

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

View file

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

View file

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

View file

@ -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',

View file

@ -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',

View file

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

View file

@ -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',

View file

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

View file

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

View file

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

View file

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

View file

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

View 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');
}
}
}

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

View 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');
}
}
}

View file

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

View file

@ -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',

View file

@ -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',

View file

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

View 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!;
}
}

View 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(),
),
);
}
}

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

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

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

View 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!};
}

View 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';
}
}

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

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

View file

@ -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"),

View file

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

View file

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

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

View 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';

View file

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