Commit graph

1072 commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
NBA2K1
8a2a57fbe5 Reduce Code Duplication in novel_reader_view.dart
Why duplicate the entire `ReaderAutoScrollButton` inside novel_reader_view.dart??
2026-04-26 21:55:00 +02:00
NBA2K1
b1459cffc1 Reduce Code Duplication in novel_reader_view.dart
Why duplicate the entire `ReaderAppBar` inside novel_reader_view.dart??
2026-04-26 21:50:22 +02:00
NBA2K1
dd6ff2580a Use Platform Helper Everywhere Possible 2026-04-26 19:42:13 +02:00
NBA2K1
5bab1492a4 Reduce Code Duplication by Adding Platform Helper
New file `lib/utils/platform_utils.dart`, to stop defining isDesktop and isMobile everywhere.
2026-04-26 19:39:14 +02:00
NBA2K1
ffa8f15c88 ReaderModeExtension to Reduce Code Duplication 2026-04-26 19:12:56 +02:00
NBA2K1
c09eb5351d Reduce Code Duplication in reader_view.dart
extract helper method `_goToChapter()`, to reduce the code duplication in the `ReaderKeyboardHandler` and `ReaderBottomBar` call.
2026-04-26 17:45:02 +02:00
NBA2K1
3aa5c73dba Improve Performance
`_resize(fit)` was called on every Build.
`_resize` posts a WidgetsBinding frame callback unconditionally. If fit hasn't changed, this is wasted work.
2026-04-26 17:38:26 +02:00
NBA2K1
47f3296e9e Reduce Code Duplication in anime_player_view.dart
by extracting the helper methods `_seekTo()` and `_seekBy()`.
This also fixes a potential bug, where in line 1634 it was calculated
`skipDuration - _currentPosition.value.inSeconds` instead of the other way around.
That doesn't make sense. If currentPosition = 120 and skipDuration = 10, this becomes:
`_tempPosition = Duration(seconds: 10 - 120)`; so `= Duration(seconds: -110)`
A negative duration makes no sense as a UI indicator of a seek target.
2026-04-26 15:28:52 +02:00
NBA2K1
cc189fd4e3 Reduce Code Duplication in anime_player_view.dart
by extracting the same MPV Event Handler Boilerplate into a helper method.
2026-04-26 15:05:23 +02:00
NBA2K1
b05c17518f Reduce Code Duplication Across 3 Files
By extracting:

```
      SystemChrome.setEnabledSystemUIMode(
        SystemUiMode.manual,
        overlays: SystemUiOverlay.values,
      );
```

to a file `system_ui.dart` and calling the method `restoreSystemUI()`
2026-04-26 15:03:56 +02:00
NBA2K1
d625b9c77d Improve EndOfMangaCard
When in vertical mode, it now shows the last_page icon pointing down,
2026-04-26 03:32:28 +02:00
NBA2K1
a9e307b2a4 Add multi-season support and split parse methods
Changes:
- Add season-keyword regex (staffel, season, saison, temporada) and
  episode-keyword regex (folge, episode, ep.) to reliably extract
  the correct number regardless of name format
- parseChapterNumber() now encodes season context into the sort key
  (season * 100000 + episode) so multi-season anime sort correctly
  across seasons without mixing episode numbers
- Add parseEpisodeNumber() which strips season context and returns
  only the episode number within a season; use this for tracker
  updates (MAL/AniList/Kitsu) and AniSkip lookups, where the tracker
  entry is already season-specific
- Switch updateTrackChapterRead and getAniSkipResults to
  parseEpisodeNumber to fix incorrect episode reporting for
  multi-season anime
- Compile all RegExp objects as static finals instead of per-call
  instantiation
- Refactor duplicated parse logic into a single private _parse()
  method with an applySeason flag
2026-04-26 03:32:28 +02:00
NBA2K1
29f202d31d remove redundant methods
`_getFilteredAndSortedChapters()` and `_filterAndSortChapter()` are duplicates of `getFilteredChapterList()` from MangaExtensions.
2026-04-26 03:25:59 +02:00
NBA2K1
4a44dd6658 New Setting: Mark duplicate chapter as read
When this setting is turned on, the app will automatically mark chapters as read if they have the same chapter number as a chapter you've already read.

This is useful for sources where:

- multiple scanlators upload the same chapter number
- duplicate entries appear due to different naming formats
- a series has repeated or alternative releases of the same chapter

With the setting enabled, you won’t have to manually mark each duplicate as read.
2026-04-26 01:09:28 +02:00
NBA2K1
8ac25750a2 Why reverse the reading list and then calculate -1 2026-04-25 00:15:13 +02:00
NBA2K1
3434a2b16e Add Copilot suggested change (3/3)
https://github.com/kodjodevf/mangayomi/pull/714#discussion_r3139743045
2026-04-24 22:23:57 +02:00
NBA2K1
519eb9d589
Add Copilot suggested change (2/3)
https://github.com/kodjodevf/mangayomi/pull/714#discussion_r3139743018

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-24 22:22:56 +02:00
NBA2K1
518b484a69 Add Copilot suggested change (1/3)
https://github.com/kodjodevf/mangayomi/pull/714#discussion_r3139743002
2026-04-24 22:21:41 +02:00
Moustapha Kodjo Amadou
85f154d738
Merge pull request #713 from NBA2K1/Reorder-code
Reorder code
2026-04-24 19:52:11 +01:00
NBA2K1
4077511022 Improve chapter sorting & update logic
- Add ChapterRecognition for numeric chapter parsing
- Replace old comparators with chapter‑number–aware sorting
- Unify sort modes and simplify list handling
- Rewrite updateMangaDetail with URL‑based dedupe
- Preserve read state across scanlators
- Update existing chapters instead of recreating
- Only create Update entries for new unread chapters
- Recompute smartUpdateDays using combined chapter list
- Remove outdated reversed/index‑based logic
2026-04-24 19:00:10 +02:00
NBA2K1
8adb8bee17 reduce memory usage
`chapters.length - chapters.reversed.toList().indexOf(chapters.reversed.toList()[finalIndex]) - 1;`
is just the same as
`chapters.length - 1 - finalIndex`
but it creates two lists, which is wasteful.
2026-04-24 00:50:22 +02:00
NBA2K1
0a2c8e2649 rename files manga.dart and chapter.dart 2026-04-23 18:53:25 +02:00
Moustapha Kodjo Amadou
67680cbfb9
Merge pull request #712 from NBA2K1/fix-logger-exception
Fix AppLogger Exception
2026-04-21 23:07:09 +01:00
NBA2K1
6297467c63 Use String Extension instead of private function 2026-04-21 22:59:19 +02:00
NBA2K1
148943093f Move String Extension to correct place 2026-04-21 22:59:12 +02:00
NBA2K1
a381e7baa7 Fix Exception
- Replace synchronous Isar writes with async transactions in `AboutScreen`
- Make `AppLogger.init()` and `dispose()` fully async and guarded with `_busy`
- Remove `StreamController` queue and write directly to the IOSink
- Prevent double‑initialization and race conditions using `_initialized` + `_busy`
- Update log toggle handler to await logger init/dispose for consistency

Fix following Exception when disabling and re-enabling logger:

```
Exception has occurred.
StateError (Bad state: Stream has already been listened to.)
```
2026-04-21 21:45:40 +02:00
NBA2K1
5104f6c19b Fix ChapterTransitionPage layout exceptions
The horizontal layout (_buildHorizontalLayout) used FittedBox as a
shared scaffold for both vertical and horizontal reading modes.
FittedBox passes unbounded width constraints to its child, which caused
RenderFlex to throw "children have non-zero flex but incoming width
constraints are unbounded" when Row + Expanded children were used.
This cascaded into a chain of "RenderBox was not laid out" assertions
all the way up through IntrinsicHeight, ConstrainedBox, FittedBox,
LayoutBuilder, and into the ScrollablePositionedList in
image_view_webtoon.dart.

Fix: split the two layout paths before they reach a scaffold widget.
The vertical path keeps FittedBox (safe, maxWidth is capped at 480px).
The horizontal path uses SizedBox(width: MediaQuery screenWidth) so
Row/Expanded always receive a finite width constraint.
2026-04-21 21:33:16 +02:00
Moustapha Kodjo Amadou
853a9d599b v0.7.60 2026-04-21 17:05:15 +01:00