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.
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.
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.
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.
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.
- Updated Dart SDK version to ^3.13.0 in pubspec.yaml
- Updated various package versions in pubspec.lock
- Changed macOS platform version from 10.15 to 12.0 in Podfile and project.pbxproj
- Added cupertino_ui dependency
- Refactored code for better readability by removing unnecessary line breaks
The interface-scale option was gated to TV. Generalize it to every platform:
TV keeps the fixed-reference-width normalization (for density consistency),
while desktop/tablet/phone get a straight multiplier on the native size (1.0 is
native, higher zooms in). Renamed TvUiScale -> AppUiScale and the setting /
slider accordingly, and the slider now shows in Appearance on all platforms.
Addresses #589 (scale the app UI while navigating, especially on big screens).
A TvPill fired its onTap on any Select KeyUp, even one whose KeyDown landed on a
different control. Activating something that removes itself (the source page's
search button) reverts focus to the previously focused pill, and the trailing
KeyUp then re-selected it. Only act on a KeyUp that has its matching KeyDown on
the same pill, matching the cover-card guard.
TVs report display density wildly inconsistently, so the fixed-dp layout is
comfortable on one box and zoomed-in on another. Normalize the whole TV UI to a
fixed 1280 logical reference width (== Android density 240 on a 1080p panel) and
scale it uniformly to fill the real panel, so it looks the same on every TV
regardless of reported density. Add a 'TV interface scale' slider (85 to 125
percent, persisted on the Isar Settings collection) so users can fine-tune for
their screen and viewing distance. No-op off TV.
- TV player settings: the quality/subtitle/audio pages were built once and never
rebuilt, so after switching a track the check mark stayed on the old option.
Wrap them in a StreamBuilder on player.stream.track so the list recomputes from
live player state. Also autofocus the first row of every drilled-in page so a
row is highlighted immediately instead of focus landing nowhere.
- TvEscapableSlider draws an accent tint and ring while focused, since the inner
Slider (excluded from focus) gave no cue of which slider the d-pad was on.
- Statistics and Calendar: on the anime-only TV layout, force the anime tab
instead of intersecting with hiddenItemTypes. When the Anime library was
hidden from that list the intersection was empty, blanking Statistics (it hit
a leftover debug placeholder, now a proper empty state) and leaving Calendar
with a null item type and no data.
- Covers: ignore a Select KeyUp that has no matching KeyDown on the same card.
A detail screen's Back button pops on KeyDown and the trailing KeyUp landed on
whatever cover regained focus, immediately re-opening the detail just closed.
- TV anime detail: only reclaim focus when it is the current route. It watches a
live chapters stream and rebuilt while Migrate/Mass migration/Recommendations/
Tracking sat on top, stealing focus back to Continue so those screens never
held focus and OK played a random episode.
- TV anime detail: add a Tracking action opening the same tracker sheet as the
classic detail (extracted into a shared openTrackingMenu), or routing to the
tracking settings when no tracker is logged in.
- Migrate: claim the initial TV focus from the first source that returns results
rather than always source #0, which could be empty.
- General settings: wrap the four recommendation-weight sliders in
TvEscapableSlider so the d-pad can leave them.
Builds on the merged TV base (#799): adds the pieces that did not land there.
- Shared TV widgets: TvPill, TvRowButton/TvListRow (band + fade focus),
TvMenu (centred d-pad menus replacing anchored dropdowns).
- Dedicated d-pad anime detail (hero + vertical action list).
- Nav rail branded with the app glyph, sized/spaced/animated for a TV.
- Browse/Updates/History/Source: home-style pills, focusable rows with the lit
band, accent focus tint on top-bar icons.
- Global-search + library covers d-pad reachable and scale on focus.
- Centred menus for the source, download queue, detail and library overflows,
and the source filter sheet.
- Settings + menu rows get the accent band; app-wide TV focus tint.
- Error/empty pushed screens autofocus a back button; held-OK long-press so
multi-select is reachable with a remote.
TV-only (isTv); phones and desktop unchanged.
Matrix4.scale is deprecated in favour of the explicit per-axis form. For a
double it just forwards to scaleByDouble(x, y ?? x, z ?? x, 1.0), so calling
scaleByDouble directly is the same transform. Hoist the factor into a local so
the ternary is not repeated once per axis.
Also drop the dead optional focusNode on _TvSeekBar: no call site ever passed
it, and its getter already falls back to a node of its own.
Leaves the branch with no analyzer warnings, and no deprecations beyond the
WillPopScope in webview.dart that main carries already.
Reword the TV comments to describe behaviour without naming Netflix or YouTube,
and replace em dashes with hyphens (including two decoder labels). No logic
change; comments and label text only.
Rows-based TV home (hero + Continue/New/Recently Added/category rows), d-pad
library grid + focus ring, tab-rail focus, anime-only layout across
Library/Browse/History/Updates/Categories, escapable settings sliders, and no
autofocus on dialog text fields for the on-screen keyboard.
Detect Android TV / leanback natively (MainActivity method channel) and expose
it as isTv; init at startup. Add the anime-only / TV-home / TV-player style
providers with an explicit user override so a wrong detection is never a trap.
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
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.
- new file item_type_filters.dart for the hiddenItemTypes function.
- reduces code duplication in statistics_screen, categories_screen and base_library_tab_screen
- 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
- 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.
- allow multiple local folders
- added support for scanning .epub novels
- added metadata,json support
- scanned entries now appear in browse screen instead of the default library category (can be added to library)