On macOS, the libmdbx / Isar database lives under
`getApplicationDocumentsDirectory()` -> `~/Documents/...`. With iCloud
Drive's "Desktop & Documents Folders" sync enabled (a common default),
macOS protects ~/Documents with TCC and denies unsigned / sideloaded /
dev / not-yet-permission-granted builds the file access libmdbx needs
to open its database. The result is a black screen on launch with the
following error in the Flutter / app log:
[ERROR:flutter/runtime/dart_isolate.cc(1402)] Unhandled exception:
IsarError: Cannot open Environment: MdbxError (13): Permission denied
POSIX errno 13 is EACCES, raised by the OS for the access denial — not
errno 15 (ENOTBLK / "Block device required"), and not iCloud "Optimise
Mac Storage" evicting files. Verified on macOS 26.3 / Apple Silicon
with iCloud Desktop & Documents sync active: a Terminal `mkdir`+`echo
> file` to the same path succeeds (Terminal inherits the user's TCC
grant), but the unsigned dev build fails on first DB open with the
error above.
Fix: on macOS only, host the database under `getApplicationSupport-
Directory()` -> `~/Library/Application Support/<bundle id>/...`. That
location is app-private, not TCC-gated, and Apple's recommended
location for app data files. iOS, Windows, Linux are unchanged — they
keep using Documents (iOS for Files-app visibility next to backups,
Windows / Linux because Documents is the conventional location and
neither has TCC).
Includes a one-shot best-effort migration: existing macOS users with a
DB at `~/Documents/Mangayomi/databases/` have it renamed to the new
path on first launch. Migration is skipped if the new location is
non-empty so we never overwrite user data, and any failure falls back
to a fresh DB rather than crashing on launch (the user can then move
the legacy directory manually if needed). Subsequent launches skip the
migration branch because the new path already exists.
Repro
- macOS with iCloud Drive's "Desktop & Documents Folders" sync enabled
- Unsigned / sideloaded / dev build of Mangayomi (or signed build that
hasn't yet received the user's "Files and Folders > Documents" TCC
grant)
- Launch -> black screen, IsarError MdbxError (13)
Verification
- Reproduced the exact error on this branch's parent commit
(upstream/main 25c1d72c) on macOS 26.3, iCloud Desktop & Documents
sync active, captured `MdbxError (13): Permission denied`
- After this patch the same build launches cleanly and opens the
database at `~/Library/Application Support/<bundle>/Mangayomi/
databases/mangayomiDb.isar`
- Existing 15 MB Isar database from a prior run preserved through the
rebuild — no data loss
Notes
- This is a narrower follow-up to the earlier proposed Application-
Support move that was correctly rejected for being cross-platform
and missing migration. This change is gated by `Platform.isMacOS`
and migrates existing macOS users.
- Hive (`Hive.initFlutter` in main.dart) still uses Documents on
macOS. It is initialized after Isar via `_postLaunchInit` and is
unawaited, so a Hive failure wouldn't reproduce the black screen.
If Hive turns out to be affected by the same TCC denial, a
follow-up PR can move it the same way.
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.
Use the already-existing downloadedChapterIdsProvider and do a simple Set.contains() lookup instead of the synchronous Isar query.
Performance improvement
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.
`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 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.
By extracting:
```
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: SystemUiOverlay.values,
);
```
to a file `system_ui.dart` and calling the method `restoreSystemUI()`
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
- remove the reverse parameter because false is already the model default, so passing it is redundant.
- flip the reverse bool, to keep the chapter sorting of already added manga the same.
Otherwise the user would have to change the sorting orientation for the chapters in the library.
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.