Commit graph

1319 commits

Author SHA1 Message Date
NBA2K1
f32f02a2b6 Fix doc comments 2026-08-18 03:44:19 +02:00
NBA2K1
0266433f95 Evict old chapter pages in continuous mode
Adds eviction + reload logic to the continuous/webtoon scroll handler.
Previously, `_readProgressListener()` triggered next‑chapter preloading
but never called `_checkAndReloadEvictedPages()` or `preloadManager.evictOldChapters()`,
so old chapters accumulated in memory.

This patch mirrors the paged‑mode behavior:

- Reload evicted pages for the current chapter
- Evict pages from older chapters
- Remove corresponding crop‑border entries
- Prefetch pages in order

All actions are gated behind `pageChanged` to avoid repeated linear scans during flings.
2026-08-18 02:49:11 +02:00
NBA2K1
70c6ac7931 Add evicted chapter tracking + O(1) checks
Introduce `_evictedChapterIds` to track chapters whose page data has been evicted, enabling fast O(1) checks instead of scanning all pages.
Integrate eviction tracking into preload/eviction flows, clear the set on resets, and expose `isChapterEvicted` through both the manager and `ReaderMemoryManagement`.
Update `ReaderView` to skip reload work when a chapter is known not to be evicted.
2026-08-18 02:23:28 +02:00
Moustapha Kodjo Amadou
8c260da306 fix: handle loading state in error scenarios 2026-08-17 11:30:39 +01:00
Moustapha Kodjo Amadou
014b84fb41 fix: improve URL construction for manga source links 2026-08-17 11:18:58 +01:00
Moustapha Kodjo Amadou
058b233338
Merge pull request #875 from Schnitzel5/fix/ios-15-avif
support render for AVIF images on iOS <16
2026-08-17 11:04:26 +01:00
Moustapha Kodjo Amadou
a2ce9e610f
Merge pull request #874 from mrandhawa14/perf/grouped-list-sort
perf: stop the grouped list re-sorting on every rebuild
2026-08-17 08:49:29 +01:00
Schnitzel5
d9abadbb96 support render for AVIF images on iOS <16 2026-08-17 00:30:37 +02:00
Mehakdeep Singh
999acf4a5c perf: stop the grouped list re-sorting on every rebuild
The memo in didUpdateWidget compared elements by identity. Every caller
collects into a freshly built list inside its own build, so identity
never matched, the group keys were recomputed and the whole list sorted
again on each rebuild.

Comparing contents instead. listEquals walks the list once, which is
nothing beside sorting it, and it matches what callers actually do.

The size of it: five rebuilds of a 50,000 entry list went from roughly
290ms to 70ms here, and only the first of those five now does any work.
That list is not hypothetical. A repo with broken source JSON generated
an entry per language and produced tens of thousands of them, at which
point the list stopped painting and would not scroll.

Tests count groupBy calls rather than timing anything, since the cost is
dominated by the comparator and a clock makes for a flaky test. One
covers a rebuild with equal contents, one covers changed contents still
being sorted, and one pins the order so the memo cannot quietly change
what is shown.
2026-08-16 14:24:00 -07:00
Moustapha Kodjo Amadou
cb06414557
Merge pull request #871 from Schnitzel5/setting/zero-auto-start
added toggle to auto start Zero
2026-08-16 16:15:16 +01:00
Schnitzel5
5cb70423c3 fixed flickering when switching to another page
- seems like it re-renders the image despite not zoomed in on iOS devices
2026-08-16 02:42:02 +02:00
Schnitzel5
437124f841 added toggle to auto start Zero 2026-08-16 02:36:32 +02:00
Moustapha Kodjo Amadou
675a971122
Merge pull request #868 from Schnitzel5/feature/virtual-local-folders
adding virtual local folders
2026-08-15 21:23:48 +01:00
Schnitzel5
354693f924 fix: use source type values when editing extensions 2026-08-15 18:28:13 +02:00
Schnitzel5
80c5a1c619 adding PiP for Android and iOS 2026-08-15 17:56:57 +02:00
Schnitzel5
62b7e68f25 Resolve virtual folders rebase conflicts 2026-08-15 17:56:49 +02:00
Schnitzel5
afb7cf1c9d optimized performance 2026-08-15 17:56:49 +02:00
Schnitzel5
73274097b9 fixing mobile external usb 2026-08-15 17:55:53 +02:00
Schnitzel5
2c70f2f224 fixed file scanning issue on darwin systems 2026-08-15 17:54:19 +02:00
Schnitzel5
c11b8751c3 adding virtual local folders 2026-08-15 17:52:37 +02:00
Moustapha Kodjo Amadou
a9b8fc0e23
Merge pull request #864 from mrandhawa14/fix/browse-and-watch-order-ui
fix: watch order timeline and global search results
2026-08-15 13:19:21 +01:00
Mehakdeep Singh
822b29c6f3 fix: stop global search fighting the scroll
Grouping the sources with nothing to show left them rendering zero height
rows inside a lazy list that estimates extents. Scrolling up built more
rows, they resolved, they collapsed to nothing, and the content shrank
under the scroll position, so the view fought the finger and the top was
only reachable by flinging hard on the first try.

Every row is built now instead of lazily, which keeps the extent stable
once each source resolves. There is one row per installed source, each is
light, and a global search queries all of them anyway.

The tradeoff is that every source now starts its search when the screen
opens, rather than as it scrolls into view.
2026-08-14 19:42:17 -07:00
Mehakdeep Singh
e8fc5b1a50 Merge branches 'fix/watch-order-focus-current' and 'fix/global-search-failed-sources' into fix/browse-and-watch-order-ui 2026-08-14 19:38:20 -07:00
Mehakdeep Singh
edeca284d9 fix: stop the rail stretching watch order covers
Each cover sat directly inside SizedBox(width: 132), which hands its
child a tight width. That silently overrode the cover's own width, so
every cover rendered 132 wide and only its height ever changed. The
covers looked near square, the current entry and the rest differed in
height alone, and editing the widths did nothing at all.

Centring the cover inside the rail restores its own width. The connector
stub below it was already centred; the cover was not.

The aspect now comes from the artwork rather than being chosen: AniList
serves these covers at 230x320, so the box uses 320/230 and BoxFit.cover
has nothing left to crop. The previous 1.46 was taller than the art and
trimmed the sides.

Covered by a test, since the failure mode is invisible in the source:
the widget asks for one width and silently gets another.
2026-08-14 19:00:43 -07:00
Mehakdeep Singh
e4efc684c5 fix: group the sources with nothing to show in global search
A source that failed held a full 260px slot showing an error panel and a
Retry, so a few broken extensions pushed the sources that did return
results off the screen. A source that found nothing held the same slot to
say so.

Both are the same non-event to someone scanning results. They are now
kept out of the list entirely and collected into one collapsed group at
the bottom, so the results are never interleaved with dead extensions.
The rows stay mounted and simply render nothing, so nothing refetches
and the group fills in as each source resolves.

The retry is gone. It made sense on a single screen, which is where the
shared ErrorState came from, but not for one source among many: these
failures are almost always a broken extension returning the same error
every time, not a transient blip. Each row in the group keeps its reason,
since that is the only clue about which extension is broken, and still
opens the source.

Also fixes every row sharing key: ValueKey(query), which is not a valid
sibling key, and drops the per row Scaffold that forced the row to expand.

Adds one plural string for the group header.
2026-08-14 17:02:47 -07:00
Moustapha Kodjo Amadou
62064d3755
Merge pull request #863 from NBA2K1/edit-metadata
Add Edit Metadata button to local extensions
2026-08-15 00:36:53 +01:00
Mehakdeep Singh
72e1c3f374 fix: one aspect for every watch order cover
The two cover sizes were not the same shape: 100x146 is 0.685 and
120x174 is 0.690, so the current entry cropped its artwork slightly
differently from every other row in the rail.

Both are now derived from a single aspect constant, so they can only
differ in size. The current entry is 10% wider rather than 20%, which is
enough to feel given the accent ring already marks it, and no longer
reads as a jump in the rail.

This replaces an earlier pass that rounded everything to a clean 2:3.
That changed the shape rather than just unifying it, and read worse on
the device.
2026-08-14 14:58:07 -07:00
Mehakdeep Singh
1890ecadc4 fix: make the watch order anchor survive a slow first frame
The scroll onto the current entry worked on macOS but not on iOS. It was
a race, not a platform difference: _didAnchorOnCurrent was set before the
attempt, and the post frame callback then returned silently when the row
was not laid out yet, so a single early miss disabled the anchor for good.

It now retries across frames until the row exists and the scroll view
knows its extent, giving up after ten. The route can still be animating
in when the data arrives, which is when the first attempt misses.
2026-08-14 14:16:04 -07:00
Mehakdeep Singh
913d96130d fix: keep the watch order focus tint off the connector
The ink surface wrapped the whole row including the connector stub, so
focusing an entry on TV or desktop painted a square tinted block that
ran down over the rail and closed up the visual gap between rows.

The connector is now drawn after the ink surface rather than inside it,
aligned under the cover by reusing the same padding and rail width. The
tint is also rounded and clipped to match, instead of being a hard
edged rectangle.
2026-08-14 14:06:08 -07:00
Mehakdeep Singh
cace9b89db fix: open the watch order on the entry you are watching
The timeline always started at the top. Mid way through a long franchise
that looks like the beginning of the list, hiding the fact that there
are earlier entries above and later ones below.

The current entry now carries a key, and a post frame
Scrollable.ensureVisible(alignment: 0.3) brings it into view with the
previous entry still partly visible above it. On TV the remote lands on
that row too, instead of row 0.

No scroll offsets are computed. The framework positions it, and both
edge cases fall out for free: nothing happens when the current entry is
already first, or when the franchise is short enough that the list
cannot scroll at all.

The rows are built eagerly rather than lazily, because a lazily built
row that is still off screen has no context for ensureVisible to reach.
A franchise timeline is short, so that is a fair trade.
2026-08-14 13:16:20 -07:00
NBA2K1
e63a857a53 Add Edit Metadata button to local extensions 2026-08-14 22:09:25 +02:00
Mehakdeep Singh
1070d5f190 feat(ios): adopt the UIScene lifecycle
The app was still on the pre-scene UIApplicationDelegate lifecycle. That
is what disabled Picture in Picture: PiP opens a second UIScene, which
re-entered didFinishLaunchingWithOptions and re-ran plugin registration,
and connectivity_plus segfaulted. AppDelegate also reached the binary
messenger through window?.rootViewController as! FlutterViewController,
and under UIScene the app delegate owns no window, so that force-cast
would crash outright.

Follows the migration flutter_tools performs for unmodified templates,
which skips this project because AppDelegate is customised:

- Info.plist declares UIApplicationSceneManifest with Flutter's own
  FlutterSceneDelegate and the existing Main storyboard.
- Plugin registration and the libmtorrentserver channel move into
  didInitializeImplicitFlutterEngine, which fires once per engine rather
  than once per scene. The channel takes its messenger from the engine
  bridge's applicationRegistrar, so the force-cast is gone.
- The manual AppLinks.shared.getLink(launchOptions:) block is removed.
  app_links already conforms to FlutterSceneLifeCycleDelegate and
  FlutterSceneDelegate forwards scene:willConnectToSession:options: to
  it, so it picks up cold-start and warm links itself. Those launch
  options are never populated under UIScene anyway.
- UIBackgroundModes gains audio, needed for PiP and background playback.

PiP itself is NOT re-enabled here. Its original blocker is fixed, but
the media_kit fork has since moved the API: enableAutoPictureInPicture
no longer exists and PiP now lives on VideoController.pictureInPicture
as a controller taking a native libmpv handle. That port needs device
testing, so it gets its own change. The stale comments saying to
re-enable PiP "after the UIScene migration" are corrected to say this.

Needs verification in Xcode on a device: launch, deep links, the torrent
server channel, and backgrounding.
2026-08-14 12:20:57 -07:00
Moustapha Kodjo Amadou
0da72efa9e Implement advanced zoom functionality with quick scaling and improved animation handling in ImageViewWebtoon 2026-08-14 16:39:34 +01:00
Moustapha Kodjo Amadou
95ad81fcdf Refactor fullscreen toggle in GeneralTab to conditionally render on non-mobile devices 2026-08-14 16:06:11 +01:00
Schnitzel5
e11b56dba5 updated pubspec + some fixes 2026-08-14 15:38:39 +02:00
Moustapha Kodjo Amadou
165c5fc2d6
Merge pull request #860 from mrandhawa14/fix/tv-and-display-scaling
fix: make the TV layout testable and stop the scaled UI clipping on desktop
2026-08-14 14:00:36 +01:00
Mehakdeep Singh
dc2c5ba783 fix: surface swallowed failures and stop hardcoding neutrals
Four silent catches on network paths now log instead of discarding the
error. Each had a user visible consequence and no way to diagnose it:
a failed source filter drops sources from the list, a failed epub parse
shows the not-found placeholder, a failed index_v2 redirect makes a repo
look empty, and a failed repo fetch shows no sources at all. They go to
AppLogger, which is off unless the user enables logs, so nothing changes
by default and the failure is recoverable from a bug report.

The other empty catches are left alone deliberately: teardown paths like
stopServer and disposing a headless webview, and formatting fallbacks
that already have a sane default.

Eleven hardcoded neutrals become theme colours. The palette is chosen at
runtime, so a fixed grey is wrong on some schemes: the read chapter dim
in updates, the tab bar dividers, the empty category state, the disabled
webview arrows, and the about divider.

Greys that are meant to be literal are untouched: the reader background
named "grey", the novel colour picker swatches, player chrome sitting on
video, and the icon on the sync tile's fixed dark box.
2026-08-14 03:09:08 -07:00
Mehakdeep Singh
b8f1e91985 fix: keep the scaled UI pinned to the window on desktop
Raising the interface scale on desktop left the right and bottom edges
of the window blank. AppUiScale measured MediaQuery.size, which can
differ from the constraints it is actually given there, and its FittedBox
sat under loose constraints, so it shrank to the smaller design child and
kept it in the top left instead of covering the window.

Measure the real box with a LayoutBuilder and pin the FittedBox to it
with an explicit SizedBox, so the scaled subtree always fills the window.
Safe area insets are still converted into design space, so padding stays
correct.

Validated on desktop.
2026-08-14 02:54:46 -07:00
Mehakdeep Singh
35b3b698cf feat: rebuild recommendations and watch order for TV
Both screens were unusable with a remote. Neither had a single autofocus
or any isTv branch, so opening either left nothing focused: the d-pad did
nothing until the user guessed a direction, with no ring to show where
they were. Their rows also stretched the full width of a TV or desktop
panel, and the watch order role badges were hardcoded Colors.grey and
Colors.blueGrey, which washed out on dark themes with a light accent.

Recommendations becomes a poster list: cover, an accent score pill, the
title, a two line capped synopsis and genre chips. It stays one column on
a phone and reflows to two above 700px, capped at two, so rows never run
into empty space.

Watch order becomes a vertical timeline rail on every form factor,
centred at 820px on wide windows. The current entry is enlarged with an
accent ring. Role badges are now theme safe: "currently watching" fills
with the accent and computes its label colour from that fill's luminance
rather than the app brightness, "up next" is accent tinted, "previous" is
a neutral tint of the foreground.

The first card or row claims focus on TV, so a remote lands somewhere.

Both files now share one set of alpha constants. They previously
disagreed six ways about the same concepts: focus wash 0.16 against 0.12,
neutral tint 0.08 against 0.09, secondary text 0.7 against 0.6, badge
radius 5 against 6, cover aspect 0.681 against 0.685 and 0.690, and three
sub pixel type steps. Covers are 2:3 at both sizes and the type scale is
whole pixels.
2026-08-14 02:54:34 -07:00
Mehakdeep Singh
ad57e7013a feat: show a cover grid skeleton while a source page loads
Fetching a source page is the app's longest routine wait and it showed a
centred spinner, which says nothing about what is coming and reads as a
stall.

CoverGridSkeleton lays out the shape the covers will land in. It renders
through the real GridViewWidget with the same gridSize and aspect ratio,
so the delegate, spacing, padding and TV insets cannot drift from the
loaded grid and nothing shifts when the data arrives.

The tint is the theme foreground at low alpha rather than a fixed grey,
since the palette is chosen at runtime. The pulse rebuilds a dozen plain
boxes instead of wrapping the grid in an Opacity, which would force a
save layer every frame, and it holds a flat tint when the platform asks
for reduced motion.

List display keeps the spinner: it has no fixed cell shape to stand in
for.
2026-08-14 02:54:34 -07:00
Mehakdeep Singh
b0896f2cf8 refactor: use the shared error state across screens
Nine screens rendered a bare Center(child: Text(error.toString())), so a
failure showed the user a stack trace with nothing to do about it, and
looked different in every place it appeared. They now use ErrorState,
each wired to a retry that re-runs the work that failed: invalidate the
provider for the AsyncValue screens, reset the flags and call the init
again for the ones holding _errorMessage in state.

retry was already translated into fifteen languages but only reachable
from reader image retries and the download queue. It is now offered
wherever a screen fails.

manga_home_screen keeps its own layout: it already has source specific
recovery (refresh through the right provider, and a webview for
Cloudflare), so only the raw cause is demoted to match.

ErrorState gains autofocusRetry because the anime player deliberately
autofocuses its back button for TV, and two widgets asking for autofocus
in one scope resolve arbitrarily.
2026-08-14 02:54:34 -07:00
Moustapha Kodjo Amadou
45871f9351
Merge pull request #853 from mrandhawa14/fix/root-error-state
fix: show a real error state when startup fails
2026-08-14 10:12:41 +01:00
Moustapha Kodjo Amadou
6e31a63e11
Merge pull request #852 from NBA2K1/Code-editor-performance
Better performance in code editor
2026-08-14 10:09:55 +01:00
Moustapha Kodjo Amadou
323335db4d
Merge pull request #851 from NBA2K1/hide-mihon-warning-banner
hide warning when no mihon extension is installed pt.2
2026-08-14 10:09:23 +01:00
Mehakdeep Singh
9ed8607e49 fix: show a real error state when startup fails
The root shell rendered LoadingIcon for both the loading and the error
branch of migrationProvider, so a failed migration left the app on a
blank splash forever with no message, no cause and nothing to act on.

Add a shared ErrorState widget (readable line, the technical cause kept
but demoted, optional retry) and use it for the error branch, wired to
re-run the migration. Screens currently hand-roll
Center(child: Text(error.toString())) in ten places; those move over
separately.

LoadingIcon also hardcoded a white background with a black icon, which
meant a full screen white flash on every cold start under a dark theme.
Both colours now come from the active scheme.
2026-08-14 01:30:56 -07:00
NBA2K1
69811c4c51 Better performance in code editor
Before, every keystroke was saving to the DB.
Now there is a debounce timer, so it only saves, when the user is pausing for 1.5 seconds.
Until then, the keystrokes are saved to memory.
Also saves on exit, in dispose.
2026-08-13 21:49:16 +02:00
NBA2K1
831eaa6da4 hide warning when no mihon extension is installed 2026-08-13 21:20:25 +02:00
NBA2K1
1577877e3b re-add "NSFW (18+)" badge to extension detail 2026-08-13 20:55:12 +02:00
NBA2K1
107548642f re-add isNSFW to extension creation 2026-08-13 20:54:59 +02:00
Moustapha Kodjo Amadou
b08ebcd86c fix Restoring Backup deletes all manga library entries #848 2026-08-13 15:15:11 +01:00