`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.
By extracting:
```
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: SystemUiOverlay.values,
);
```
to a file `system_ui.dart` and calling the method `restoreSystemUI()`
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.
- 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
`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.
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.
Replace per-widget Isar StreamBuilder usage with a single shared
library index managed in MangaHomeScreen.
- Subscribe once to manga updates and build a name -> Manga map
- Pass libraryManga down to card widgets instead of querying per item
- Remove StreamBuilder logic from MangaImageCardWidget and list tile
- Use library data for cover, tracker image, and favorite state
- Add favorite overlay indicator based on libraryManga
- Clean up redundant filtering and improve performance
This reduces rebuild overhead and avoids multiple database listeners
per list/grid item.
`addListener` is called inside the `data:` callback of `build()`.
Every single time the widget rebuilds, which happens on scroll, on tab switch,
on any `setState`, it adds *another* listener to `_scrollController`,
stacking up indefinitely. With 100 items and active scrolling,
you can easily accumulate hundreds of listeners,
each one firing `setState` + `_loadMore()` on every scroll-to-bottom.
Fix: Move the listener setup to `initState()` and remove it in `dispose()`