Commit graph

1993 commits

Author SHA1 Message Date
Moustapha Kodjo Amadou
2d9eebe94e
Merge pull request #728 from mrandhawa14/fix/cover-decode-resize
perf(library): decode covers at thumbnail resolution to cut image-cache RAM (refs #609)
2026-05-11 09:00:42 +01:00
Moustapha Kodjo Amadou
6449cfdd5a + 2026-05-11 08:59:18 +01:00
Mehakdeep Singh
e16f0ba9be Merge remote-tracking branch 'upstream/main' into fix/cover-decode-resize
# Conflicts:
#	lib/modules/library/widgets/library_gridview_widget.dart
#	lib/modules/library/widgets/library_listview_widget.dart
2026-05-11 00:56:00 -07:00
Moustapha Kodjo Amadou
a961834a88
Merge pull request #723 from NBA2K1/reduce-code-duplication
New file to reduce code duplication
2026-05-11 08:42:22 +01:00
Moustapha Kodjo Amadou
b507f17846
Merge pull request #719 from NBA2K1/main
Make Code More Readable
2026-05-11 08:38:24 +01:00
Moustapha Kodjo Amadou
e345c68d83
Merge pull request #727 from mrandhawa14/fix/image-cache-cap
perf(images): cap decoded image cache by platform (refs #609)
2026-05-11 08:36:33 +01:00
Moustapha Kodjo Amadou
8cc66e1f72
Merge pull request #726 from mrandhawa14/fix/statistics-memory
perf(statistics): aggregate inside Isar to fix #543 crash on large libraries
2026-05-11 08:34:05 +01:00
Moustapha Kodjo Amadou
cf86d63ac4
Merge pull request #724 from mrandhawa14/fix/anime-download-stall
fix(downloads): unstall anime downloads on URLs with query strings + 206 responses
2026-05-11 08:30:49 +01:00
Mehakdeep Singh
861ee65113 perf(library): decode covers at thumbnail resolution to cut image-cache RAM
Refs #609 (high RAM with stutters).

Manga / anime covers from sources are typically 720x1080 or larger
(~3 MB decoded RGBA per cover). The library grid, library list and
generic browse / search card widgets render those covers at roughly
150x220 logical pixels, but every cover decoded to its full source
resolution and that decoded bitmap landed in Flutter's `imageCache`.
With 30-50 covers in flight during a normal scroll, the default
100 MB cache filled and the engine started evicting + re-decoding
aggressively — exactly the symptom in #609 (stutters + high RAM).

Mangayomi already had `ExtendedResizeImage` available via the
`extended_image_library` package and used it in one place
(`cachedCompressedNetworkImage`, called only from the History
screen). This commit generalises that pattern.

Add a `coverProvider()` helper in `lib/utils/cached_network.dart`
that wraps `CustomExtendedNetworkImageProvider` in
`ExtendedResizeImage` with a 200 KB encoded budget — sharp at
typical thumbnail size on high-DPR screens, ~3.6x smaller decoded
than a full-resolution cover. Pass through the same `cache` /
`cacheMaxAge` knobs the underlying provider exposes so existing
disk-cache behaviour is preserved.

Swap the three high-traffic thumbnail call sites to use it:

* `lib/modules/library/widgets/library_gridview_widget.dart`
* `lib/modules/library/widgets/library_listview_widget.dart`
* `lib/modules/widgets/manga_image_card_widget.dart`
  (both `MangaImageCardWidget` and `MangaImageCardListTileWidget`,
  used by browse and search results)

Deliberately not changed:

* The manga / anime detail page hero cover — large display, full
  resolution is appropriate.
* Reader pages — already memory-managed by `ChapterPreloadManager`
  and need full resolution for actual reading.
* `cachedNetworkImage()` and other lower-traffic thumbnail surfaces
  (tracker results, calendar, recommendation grid). Easy to extend
  in a follow-up if anyone asks; kept narrow here so review is
  manageable.

Verified

* `flutter analyze` clean on every touched file
* `flutter build macos --release` succeeds
* Smoke-tested on macOS with the local-all-fixes build: library
  grid, library list and browse card all render identical-looking
  covers at typical thumbnail sizes; no visible quality regression
  at the displayed scale
2026-05-09 23:37:44 -07:00
Mehakdeep Singh
7b3916a221 perf(images): cap the decoded image cache by platform
Refs #609 (high RAM with stutters).

Flutter's `imageCache.maximumSizeBytes` defaults to 100 MB. With a large
manga library and full-resolution covers (a typical 720x1080 cover is
about 3 MB decoded), scrolling fills that cap quickly and the engine
starts evicting and re-decoding aggressively — felt as stutters and
elevated heap on memory-constrained devices.

Set the cap explicitly, sized for the platform: 64 MB on mobile, 256 MB
on desktop. Mobile gets a tight ceiling so the OS does not kill the app
on heap pressure; desktop keeps a generous budget so users with very
large libraries still scroll smoothly.

This is a one-line ceiling, not a fix for the underlying decode-at-
source-resolution behaviour — that is being tracked separately and
will further reduce per-image cache pressure.

The encoded-bytes LRU cache in `CustomExtendedNetworkImageProvider`
(`_memoryCache`, 50 MB) is independent of `PaintingBinding.imageCache`
and is unaffected by this change. So is the disk cache from
`extended_image_library`. Only the in-memory cache of decoded
`ui.Image` objects is bounded here.

Verified:
- `flutter analyze` clean
- `flutter build macos --release` succeeds
- Tested on macOS — Activity Monitor shows the desktop budget is
  comfortably above what the existing UI normally needs; mobile budget
  is consistent with what the system already imposes via OOM kills.
2026-05-09 23:30:59 -07:00
Mehakdeep Singh
8babb975ab perf(statistics): aggregate inside Isar instead of loading all favourite chapters into memory
Fixes #543. The statistics screen was crashing on Android with a
populated library (~75+ favourites). Repro: open the More -> Statistics
screen on an account with a real-sized library; app hangs and is
killed.

Cause is in `getStatistics()` in `lib/modules/more/statistics/
statistics_provider.dart`. The provider materialised every favourite
manga *and* every chapter of every favourite into a Dart list, then
walked the lists in memory:

    final items = await isar.mangas.filter()...findAll();
    final chapters = await isar.chapters.filter()
        .manga((q) => q.favoriteEqualTo(true)...).findAll();

For a library with 75 favourites at ~100 chapters each that is 7,500+
heavy Isar objects materialised at once just to compute six counts.
The "completed items" loop then re-fetched chapters per completed
manga via the link relation (`item.chapters.toList()`), and the
reading-time aggregation loaded every History row to sum a single
int field.

This commit rewrites the function to do all aggregation inside Isar:

* total / read / completed-items / downloads => `count()` queries on
  indexed filter chains. No object hydration.
* completed favourites => projects only the IDs (`idProperty()`),
  then runs two cheap `count()` queries per item to check
  "has-chapters && unread == 0". Logic is unchanged from the original
  `every(isRead) && isNotEmpty` check.
* reading time => projects the `readingTimeSeconds` field
  (`readingTimeSecondsProperty()`) and folds the resulting List<int?>.
  Avoids hydrating full History rows.

Memory: peak in-memory list shrinks from ~7,500 fully-hydrated chapters
plus all favourite mangas plus all history rows, to (at most) the IDs
of completed favourites (typically <50 ints) and the projected reading-
time list (one int per history row, decoded as a flat list).

DB round-trips: the completed-items branch now does 2 round-trips per
completed favourite instead of 1 link-traversal per completed favourite.
On indexed `mangaId` + `isRead` filters this is sub-millisecond per
query and well below the cost of materialising thousands of rows. The
overall function is faster end-to-end on any non-trivial library.

Behaviour preserved: same six fields, same definitions, same edge
cases (manga with no chapters is not counted as completed).

Verified
- `flutter analyze` clean on the touched file
- `flutter build macos --release` succeeds
- Manual smoke test on macOS with the local-all-fixes build (the bug
  reproduces only with a large mobile library; macOS desktop with a
  small test library returns the same numbers as before)
2026-05-09 23:12:49 -07:00
Mehakdeep Singh
bbc7fa04f5 fix(downloads): unstall anime downloads on sources that return URLs with query strings or HTTP 206
Two related bugs left anime downloads stuck at 0% with no error visible to
the user. Manga downloads from clean HTTPS sources were unaffected.

1) lib/utils/extensions/string_extensions.dart

   isMediaVideo() did a plain endsWith on the full URL string, so URLs
   shaped like https://host/play/{id}/video.mp4?for={token} (used by
   AnimeGG and several other sources) failed the filter because of the
   trailing `?for=...` query string. With both m3u8Urls and nonM3u8Urls
   empty in downloadChapter, the surrounding
   Future.doWhile(() => isOk == true) poll never sets isOk and waits
   forever -- MDownloader is never constructed.

   Fix: match against Uri.tryParse(this)?.path instead of the full
   string, and use a leading "." in the suffix so e.g. "flashmp4" cannot
   accidentally match.

2) lib/services/download_manager/download_isolate_pool.dart

   Once the URL passes the filter, _downloadFile (anime branch) opens a
   streaming request. When the source extension sets Range: bytes=0-,
   the server correctly responds with HTTP 206 Partial Content. The
   previous "if (response.statusCode != 200)" check rejected that,
   retried 3x, and threw. The throw was masked by an outer catch(_) in
   downloadChapter, so the user only saw a forever-spinner.

   Fix: accept any 2xx (>= 200 && < 300). Same fix applied to
   _downloadSegment for HLS segment fetches.

Repro
- Source: AnimeGG (en) -- install via Mangayomi extensions
- Pick any episode (tested with Toriko Ep 147, Gintama Ep 39, Grand Blue
  Ep 12)
- Tap the download icon

Before: an empty
".../AnimeGG (EN)/<series>/<episode>.mp4"-named folder is created, the
download icon stays in the spinner state, no error toast.

After: the .mp4 is written to disk at the size declared in Content-Length
(65,026,283 bytes for Toriko Ep 147), plays in the system video player.

Tested on macOS 26.3 / Apple Silicon with AnimeGG (multiple episodes,
multiple series, including a 180 MB 720p) and a manga control
(Asura Scans, 9 pages) on the same build to confirm no regression on the
manga path.
2026-05-09 19:10:35 -07:00
NBA2K1
aeff8be3b3 refactor the widget‑wrapping logic to be cleaner
- Replace child mutation with two clearly named variables
  - This is a pure refactor:
    - base = the result of wrapping with BotToastInit
    - withBackHandler = optionally wrapped with _MouseBackButtonHandler
  - No logic changes - just clearer variable names and no reassignment.

Improves readability and maintainability.
2026-05-09 18:23:37 +02:00
NBA2K1
116ade1bb8 New file to reduce code duplication
+ listview now also shows unread chapter count like gridview did, instead of all chapter count.
2026-05-09 18:11:20 +02:00
Moustapha Kodjo Amadou
25c1d72c8b
Merge pull request #721 from NBA2K1/improve-updates
Improve updates
2026-05-09 16:07:59 +01:00
Moustapha Kodjo Amadou
e2d67e7157
Merge pull request #722 from NBA2K1/fix-exception
Fix null pointer exception
2026-05-09 16:06:47 +01:00
Moustapha Kodjo Amadou
db4a777609
Merge pull request #720 from NBA2K1/fix-downloadedChapterIdsProvider
Fix downloadedChapterIdsProvider
2026-05-09 16:04:06 +01:00
NBA2K1
caab37f662 Fix null pointer exception 2026-05-09 15:03:26 +02:00
NBA2K1
1b630954b1 Remove BuildContext from checkForUpdate Provider
Do not pass Context into any provider.

Handle logic and BotToasts in the onTap of the AboutScreen, not inside the provider.

Also make _checkUpdate() more efficient, by fetching the `/latest` API.
Before, it was fetching 10 releases and immediately discarding 9, only leaving the latest.
2026-05-09 12:59:32 +02:00
NBA2K1
27690975f4 Make _updateLibrary() fully async
also make _updateNumbers() readable.
2026-05-09 12:59:12 +02:00
NBA2K1
e0a8301595 Use downloadedChapterIdsProvider instead
Use the already-existing downloadedChapterIdsProvider and do a simple Set.contains() lookup instead of the synchronous Isar query.

Performance improvement
2026-05-09 12:52:26 +02:00
NBA2K1
134a7406d9 remove redundant helper 2026-05-09 00:36:16 +02:00
NBA2K1
0316afc2c6 Make downloadedChapterIdsProvider async 2026-05-09 00:33:36 +02:00
NBA2K1
8914357d89 Fix downloadedChapterIdsProvider
Before the "Downloaded" filter in the library AND the "Downloaded chapters" badge would not update, unless you restart the app.

Now the "Downloaded chapters" badge is increasing and the "Downloaded" filter is showing new downloaded manga chapters and hiding deleted manga chapters.
2026-05-08 23:52:52 +02:00
Moustapha Kodjo Amadou
56ab6151e6 Update 2026-05-08 15:05:03 +01:00
NBA2K1
b799a6d377 No need to reverse the list
before:
list, reverse, first
list, reverse, last

after:
list, last
list, first

Small performance improvement
2026-05-07 21:29:59 +02:00
NBA2K1
40fce92a87 Extract helper to keep build() readable
No functional change. Just a simple method extraction from inside `itemBuilder` to `_buildPagedItem()`.
2026-05-07 21:28:41 +02:00
NBA2K1
6880090f8d Reduce Code Duplication 2026-05-07 19:17:10 +02:00
NBA2K1
0672d1de67 Reduce Code Duplication 2026-05-07 19:14:50 +02:00
Moustapha Kodjo Amadou
73a7528abf
Merge pull request #717 from NBA2K1/main
Improve _handlePageNavigation()
2026-05-04 11:51:13 +01:00
NBA2K1
3fe74f8883 Clean up _setReaderMode()
No logic difference. This refactor just makes the helper more readable.
2026-04-30 19:47:46 +02:00
NBA2K1
d6bc53ab1f Add comments to ReaderModeExtension 2026-04-30 19:23:00 +02:00
NBA2K1
ffe8dee065 Improve _handlePageNavigation()
The helper now handles every continuous reading mode, not just webtoon.
2026-04-30 17:32:15 +02:00
NBA2K1
456589ccfb Use ReaderModeExtension 2026-04-30 17:30:43 +02:00
GitHub Action
d8df3a361e source update: v0.7.70 2026-04-30 13:19:10 +00:00
Moustapha Kodjo Amadou
f5ebd01dc7 v0.7.70 2026-04-30 13:58:16 +01:00
Moustapha Kodjo Amadou
fe42b43cf8 Update Dart SDK version to 3.11.5 in pubspec files 2026-04-30 13:57:16 +01:00
Moustapha Kodjo Amadou
2ecd78283a
Merge pull request #716 from 5y3b/QoF-webtoon
Refactor page navigation handling in reader view
2026-04-30 13:08:46 +01:00
abdelmonm alsnajleh
bc19e2d7cb
Merge branch 'main' into QoF-webtoon 2026-04-30 14:25:01 +03:00
Moustapha Kodjo Amadou
8c416563d4
Merge pull request #714 from NBA2K1/main
Rewrite chapter update & sorting; Preserve read state & improve ChapterRecognition & adapt EndOfMangaCard to reading mode & Clean Code
2026-04-30 11:19:11 +01:00
NBA2K1
3051c0ab97 Fix bug
Fix a rare bug in the chapter list.
2026-04-27 22:58:38 +02:00
Abdelmonm Alsnajleh
0894b94767 Refactor page navigation handling in reader view
* Extracted page navigation to its own function
* Allow for better reading in webtoon mode
2026-04-27 20:14:24 +03:00
NBA2K1
8d5aa05952 Fix potential focus flicker
`wrapWithKeyboardListener` creates a `FocusNode()` internally on every call.
If no `focusNode` is passed, a new one is allocated every rebuild, which can cause focus flicker.
Without this fix, keyboard focus can be intermittently lost after widget rebuilds,
which would silently swallow keyboard shortcuts.
2026-04-27 00:08:15 +02:00
NBA2K1
ad4207da82 Add Copilot suggested change
https://github.com/kodjodevf/mangayomi/pull/714#discussion_r3144115142
2026-04-26 23:57:23 +02:00
NBA2K1
49eed6405e Add Copilot suggested change
https://github.com/kodjodevf/mangayomi/pull/714#discussion_r3144115192
2026-04-26 23:41:35 +02:00
NBA2K1
64d22741a5 Add Copilot suggested change
https://github.com/kodjodevf/mangayomi/pull/714#discussion_r3144115131
2026-04-26 23:33:14 +02:00
NBA2K1
262fb44792 Add Copilot suggested change
https://github.com/kodjodevf/mangayomi/pull/714#discussion_r3144115200
2026-04-26 23:26:01 +02:00
NBA2K1
bf25129b56 Reduce Code Duplication in novel_reader_view.dart
`ReaderKeyboardHandler` already does everything here.
2026-04-26 23:12:54 +02:00
NBA2K1
da2682aa48 Reduce Code Duplication
by extracting `hasPreviousChapter` and `hasNextChapter` logic to the `ChapterControllerMixin`.
2026-04-26 22:49:34 +02:00
Moustapha Kodjo Amadou
40d2455247
Merge pull request #715 from NBA2K1/Mark-duplicate-chapter-numbers-as-read
New Setting: Mark duplicate chapter as read
2026-04-26 21:25:04 +01:00