Skip to content

Keep theme, library view and per-book settings per account - #1200

Closed
r0adkll wants to merge 1 commit into
refactor/settings-datastorefrom
refactor/per-user-settings
Closed

r0adkll wants to merge 1 commit into
refactor/settings-datastorefrom
refactor/per-user-settings

Conversation

@r0adkll

@r0adkll r0adkll commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Stack

Closes #607. Replaces draft #1193.

Each account now keeps its own app theme, library sorting and layout, and per-book playback speed and equalizer. Light/dark mode, listening defaults, privacy, network and the rest stay app-wide, as decided in #607.

What changed

  • Storage.

    • Each account gets its own file, datastore/users/<userId>.preferences_pb. DefaultUserSettingsStores (ported from Keep library view settings per account #1193 onto SettingsStore) keeps one open store per account for the life of the process, because DataStore allows one open store per file and the user graph is rebuilt on every session change.
    • Signed out, an in-memory store stands in, so the welcome and login screens use the default theme.
    • Unsafe characters in an account id can't escape the directory.
  • API. These are bound in the user graph on that account's store (@UserSettings):

    • LibraryViewSettings, moved from the app graph;
    • new UserThemeSettings (the app theme id, moved off ThemeSettings);
    • new PerBookSettings (per-book speeds and equalizer profiles, plus playbackSpeedFor / equalizerProfileFor and their setters, moved off PlaybackSettings and EqualizerSettings).

    The per-book setters decide in the account's write order and write the app-wide value from there when the book has no setting of its own.

  • App-wide readers. The players and the theme repository outlive the user graph, so they reach the signed-in account through SignedInSettings. It looks the user graph up in ComponentHolder on each call, so a read or write always belongs to whoever is signed in at that moment. A Scoped.onCreate attach would race the theme that LoginPresenter sets right after authentication.

    The theme repository now follows the account's theme, including across account switches.

  • Sheets. The speed and equalizer sheet components move to UserScope to read PerBookSettings.

  • Seeding. SeedUserSettingsMigration copies the account's keys, with their types, from the app-wide store the first time an account's file opens. The app-wide values stay for a release.

  • Logout. Signing out deletes the account's file. Being asked to sign in again keeps it.

  • Android backup. datastore/users/ is backed up with the app's settings. The files hold no device-bound keys.

  • Root UI. It takes LibraryViewSettings from the user graph it already receives.

Verification

  • ./scripts/ktlint --check passes. Desktop, Android (alpha debug) and iOS (:app:ios) compile, and the Metro graph checks pass.

  • ./gradlew jvmTest test passes, with the native-FFmpeg modules excluded as in Organize settings into packages and write through setters #1195. New tests:

    • PerBookSettingsTest: per-book and app-wide values across separate stores;
    • SeedUserSettingsMigrationTest;
    • DefaultUserSettingsStoresTest on real files: seeding, accounts kept apart, own file, delete then reopen, signed-out memory store, unsafe ids.
  • On device (emulator, local testbed server, fossDebug, two accounts):

    • Upgrading from the Store settings in DataStore #1199 build seeded account A's file with its Forest theme, List/Duration library and per-book 1.5x.
    • Account B was seeded the same way and then changed to Mountain / Grid / Title. Switching A ↔ B kept each account's values, with the theme changing on each switch.
    • Per-book speed stayed per account (A 1.5x, B 1.25x).
    • A cold start on B showed B's theme with no flash of A's or the default theme.
    • Signing out of B deleted its file and returned to A intact. Signing out of A left the default theme on the welcome screen.
    • No settings or DataStore errors.

    One ANR during an account switch: a Compose SnapshotStateObserver deadlock in a shared-bounds measure, with no settings code in the stack. It matches the known Compose layout regression.

Follow-ups

  • Accounts replaced without signing out (adding a second user on the same server URL) leave their settings file behind; prune leftover files.
  • The account switcher and the restore chips show the current theme for every account. They could read each account's own.
  • Remove the app-wide copies of these values, and the other legacy values, one release after DataStore ships.

🤖 Generated with Claude Code

Each account now has its own settings file, datastore/users/<id>.preferences_pb,
which DefaultUserSettingsStores keeps open for the life of the process
(DataStore allows one open store per file and the user graph is rebuilt on
every session change). Signed out, an in-memory store stands in, so the
welcome and login screens use the default theme.

- LibraryViewSettings, the new UserThemeSettings (the app theme) and the
  new PerBookSettings (per-book speed and equalizer, moved off
  PlaybackSettings and EqualizerSettings) are bound in the user graph on
  that store.
- App-wide code that outlives the user graph reaches the signed-in
  account's settings through SignedInSettings, which looks the user graph
  up in ComponentHolder on each call: the players for per-book speed and
  equalizer, and the theme repository, which now follows the account's
  theme across account switches.
- Each account starts from the values the app kept for everyone, copied
  the first time its file opens; the app-wide values stay for a release.
- Logging out deletes the account's file; being asked to sign in again
  keeps it. On Android the files are backed up with the app's settings.
- The root UI takes library view settings from the user graph it already
  receives.
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Warnings
⚠️ Big PR, try to keep changes smaller if you can
⚠️ This PR is original Xbox Huge! Consider breaking into smaller PRs
Messages
📖 This PR has been checked by Danger

Generated by 🚫 Danger Kotlin against 32983f6

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

Android APK @ 25227d4

@r0adkll

r0adkll commented Oct 6, 2026

Copy link
Copy Markdown
Owner Author

Closing: #607 is taking a simpler direction. Settings stay on the existing preference-backed store (no async API or DataStore), and only the library view settings become per-account, through user-id-prefixed keys in that same store. #1189 stays as the base.

@r0adkll r0adkll closed this Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant