Skip to content

Split CampfireSettings into focused settings interfaces - #1189

Merged
r0adkll merged 1 commit into
mainfrom
refactor/split-campfire-settings
Oct 6, 2026
Merged

r0adkll merged 1 commit into
mainfrom
refactor/split-campfire-settings

Conversation

@r0adkll

@r0adkll r0adkll commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Stack

First step of #607. CampfireSettings mixed device identity, privacy choices, app state, window layout, the socket toggle and library view preferences in one interface. This splits it by concern so each group can later move to the right storage layer (global vs per-user, then DataStore).

What changed

  • CampfireSettings is split into:
    • PrivacySettings: consent, crash/analytics reporting, keep signed in after reinstall
    • DeviceSettings: device id, analytics id, current user
    • AppStateSettings: last seen version, app update prompts, widget pinning
    • LayoutSettings: navigation rail, supporting pane width
    • ConnectionSettings: socket toggle
    • LibraryViewSettings: sort/display state, marquee, time in book, download confirmation
  • themeId, themeMode and observeTheme() move onto the existing ThemeSettings.
  • TestCampfireSettings is split the same way into Test*Settings.
  • Call sites inject only the interfaces they use. AnalyticsInitializer and PlayerWidgetComponent held a CampfireSettings they never read; those are removed.

What didn't change

The members were moved over unchanged: the var + observeX() API, AppSettings delegates, storage keys and defaults are all the same. There's no user-facing change, so no changelog entry.

Verification

  • ./scripts/ktlint --check passes.
  • :app:desktop:compileKotlin and :app:android:compileAlphaDebugKotlin compile, and the touched modules compile for the iOS simulator.
  • ./gradlew jvmTest test: everything passes except the four ffmpeg tests in infra/audioplayer/engine-tests, which fail locally because the native library doesn't load (UnsatisfiedLinkError: no jniavutil). This PR doesn't touch anything those tests use.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Warnings
⚠️

Any changes to library code should be reflected in the Changelog.

Please consider adding a note there and adhere to the Changelog Guidelines.

⚠️ 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 14c8458

@Rotom-Bot

Rotom-Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Code Coverage

File Coverage [14.58%] ❌
app/common/src/commonMain/kotlin/app/campfire/common/di/UserComponent.kt 0.00% ❌
app/common/src/commonMain/kotlin/app/campfire/common/root/CampfireContent.kt 0.00% ❌
app/common/src/commonMain/kotlin/app/campfire/common/root/MiniPlayerContent.kt 0.00% ❌
app/common/src/commonMain/kotlin/app/campfire/common/root/automation/AutomationDeepLinks.kt 93.44% ✅
app/common/src/commonMain/kotlin/app/campfire/common/root/ui/LoggedIn.kt 0.00% ❌
app/common/src/commonMain/kotlin/app/campfire/common/root/ui/LoggedOut.kt 0.00% ❌
common/compose/src/commonMain/kotlin/app/campfire/common/compose/extensions/ThemeSettings.kt 0.00% ❌
data/account/impl/src/commonMain/kotlin/app/campfire/account/DefaultAccountManager.kt 0.00% ❌
data/account/impl/src/commonMain/kotlin/app/campfire/account/backup/AccountBackups.kt 69.05% ❌
data/account/impl/src/commonMain/kotlin/app/campfire/account/restore/AccountRestoreStore.kt 97.06% ✅
data/account/impl/src/commonMain/kotlin/app/campfire/account/session/UserSessionRestorer.kt 0.00% ❌
data/analytics/impl/src/commonMain/kotlin/app/campfire/analytics/AnalyticsInitializer.kt 0.00% ❌
data/analytics/mixpanel/src/commonMain/kotlin/app/campfire/analytics/mixpanel/MixPanelInitializer.kt 0.00% ❌
features/auth/ui/src/commonMain/kotlin/app/campfire/auth/ui/consent/AnalyticConsentPresenter.kt 0.00% ❌
features/author/ui/src/commonMain/kotlin/app/campfire/author/ui/list/AuthorsPresenter.kt 0.00% ❌
features/collections/ui/src/commonMain/kotlin/app/campfire/collections/ui/list/CollectionsPresenter.kt 70.73% ❌
features/libraries/ui/src/commonMain/kotlin/app/campfire/libraries/ui/detail/book/BookPresenter.kt 86.17% ✅
features/libraries/ui/src/commonMain/kotlin/app/campfire/libraries/ui/detail/podcast/PodcastPresenter.kt 6.83% ❌
features/libraries/ui/src/commonMain/kotlin/app/campfire/libraries/ui/detail/podcast/episode/PodcastEpisodePresenter.kt 0.00% ❌
features/libraries/ui/src/commonMain/kotlin/app/campfire/libraries/ui/paged/LibraryPresenter.kt 0.00% ❌
features/playlists/ui/src/commonMain/kotlin/app/campfire/playlists/ui/detail/PlaylistDetailPresenter.kt 0.00% ❌
features/playlists/ui/src/commonMain/kotlin/app/campfire/playlists/ui/list/PlaylistsPresenter.kt 78.38% ❌
features/series/ui/src/commonMain/kotlin/app/campfire/series/ui/list/SeriesPresenter.kt 0.00% ❌
features/sessions/impl/src/commonMain/kotlin/app/campfire/sessions/network/DefaultNetworkSessionMapper.kt 0.00% ❌
features/sessions/impl/src/commonMain/kotlin/app/campfire/sessions/sync/ServerSessionAttacher.kt 0.00% ❌
features/sessions/ui/src/commonMain/kotlin/app/campfire/sessions/ui/sheets/chapters/ChapterListBottomSheet.kt 0.00% ❌
features/sessions/ui/src/commonMain/kotlin/app/campfire/sessions/ui/sheets/tracks/AudioTrackListBottomSheet.kt 0.00% ❌
features/settings/impl/src/commonMain/kotlin/app/campfire/settings/AppStateSettingsImpl.kt 0.00% ❌
features/settings/impl/src/commonMain/kotlin/app/campfire/settings/ConnectionSettingsImpl.kt 0.00% ❌
features/settings/impl/src/commonMain/kotlin/app/campfire/settings/DeviceSettingsImpl.kt 0.00% ❌
features/settings/impl/src/commonMain/kotlin/app/campfire/settings/LayoutSettingsImpl.kt 0.00% ❌
features/settings/impl/src/commonMain/kotlin/app/campfire/settings/LibraryViewSettingsImpl.kt 0.00% ❌
features/settings/impl/src/commonMain/kotlin/app/campfire/settings/PrivacySettingsImpl.kt 0.00% ❌
features/settings/impl/src/commonMain/kotlin/app/campfire/settings/ThemeSettingsImpl.kt 0.00% ❌
features/settings/ui/src/commonMain/kotlin/app/campfire/ui/settings/SettingsPresenter.kt 0.00% ❌
infra/socket/impl/src/commonMain/kotlin/app/campfire/socket/impl/DefaultSocketManager.kt 0.00% ❌
infra/updates/impl/src/commonMain/kotlin/app/campfire/updates/AppUpdateWidgetImpl.kt 0.00% ❌
infra/whats-new/impl/src/commonMain/kotlin/app/campfire/whatsnew/WhatsNewRepositoryImpl.kt 0.00% ❌
ui/navigation/ui/src/commonMain/kotlin/app/campfire/ui/navigation/NavigationPresenter.kt 0.00% ❌
ui/theming/impl/src/commonMain/kotlin/app/campfire/ui/theming/DefaultAppThemeRepository.kt 0.00% ❌
Total Project Coverage 23.48% ❌

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Android APK @ a1a60e9

CampfireSettings mixed device identity, privacy choices, app state,
layout, connection and library-view preferences in one interface. It is
now split by concern into PrivacySettings, DeviceSettings,
AppStateSettings, LayoutSettings, ConnectionSettings and
LibraryViewSettings, with the theme id and mode joining ThemeSettings,
so each can later move to the right storage layer (#607).

The property-and-observe API, delegates, storage keys and defaults are
unchanged.
@r0adkll
r0adkll force-pushed the refactor/split-campfire-settings branch from a208757 to 14c8458 Compare October 6, 2026 14:24
@r0adkll
r0adkll merged commit ab9c2c9 into main Oct 6, 2026
7 checks passed
@r0adkll
r0adkll deleted the refactor/split-campfire-settings branch October 6, 2026 14:52
r0adkll added a commit that referenced this pull request Oct 6, 2026
<!-- pr-stack:start -->
## Stack

- #1189
  - [This PR] #1204
<!-- pr-stack:end -->

Closes #607. Each account now keeps its own library sorting and layout.
Everything else stays app-wide, including the theme, and all settings
stay on the existing preference-backed store.

## What changed
- **`UserPrefixedSettings`** is an `ObservableSettings` that keeps one
account's values in the app's existing preferences, under
`user:<userId>:` keys.
- Accounts share the app's storage, and its Android backup, without
mixing with each other.
  - Its `keys`, `size` and `clear()` only see that account's settings.
- The prefix stays short because desktop's `java.util.prefs` rejects
keys longer than 80 characters. A test checks every prefixed key against
that limit.
- **`LibraryViewSettingsImpl` moves into the user graph**, on the
account's settings (`@ForScope(UserScope::class) ObservableSettings`).
Its observers run in the user graph's coroutine scope, so they end with
it.
  - Signed out, there's no account, so the app-wide values are used.
- The root UI takes the settings from the user graph it already
receives. Every other consumer was already user-scoped.
- **`DefaultUserSettingsStore`** hands each user graph its account's
settings.
- An account's settings start from the library view values the app kept
for everyone, copied the first time they're opened. Accounts added later
start from the same values.
- Logging out clears that account's keys, through the new
`UserSettingsStore` API.
- **The app's store is now qualified too:** `@ForScope(AppScope::class)
ObservableSettings` everywhere it's provided and injected, so each
injection site says which store it reads.

## Verification
- `./scripts/ktlint --check` passes. Desktop, Android (alpha debug) and
iOS (`:app:ios`) compile, and the Metro graph checks pass.
- `./gradlew jvmTest test` passes. The two native-FFmpeg audio engine
modules were excluded locally, because the bundled FFmpeg native library
doesn't load on this machine. New tests:
- `UserPrefixedSettingsTest`: per-account keys, isolation from other
accounts and the app, scoped `keys`/`size`/`clear()`, listeners;
- `DefaultUserSettingsStoreTest`: seeding once, keeping an account's
changes, accounts apart, signed out, clearing;
  - `UserPrefixedKeyLengthTest`: desktop's key length limit.
- **On device** (emulator, local testbed server, fossDebug, two
accounts):
- Upgrading from #1189 kept account A's list layout sorted by duration
and its series sort. A's prefixed copies were written, and the app-wide
values stayed in place.
- B started from the same values, then changed to grid sorted by size
and series by name. Switching A ↔ B four times showed each account's own
layout and sort immediately.
  - A cold start on B showed B's grid with no frame of A's.
- Signing out of B removed only B's keys. Signing out of A left no
per-account keys. Signing A back in started it again from the app-wide
values.
  - No settings errors or missing bindings in logcat.

## Follow-ups
- #1201: a second user on the same server URL replaces the first
account, without going through logout, so the replaced account's keys
stay behind.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

2 participants