Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ class SessionStateHolder @Inject constructor() {
/** Atomically update the state via a transform function. */
fun update(transform: (SessionState) -> SessionState) { _state.update(transform) }

/** Reset to the default [SessionState] (e.g. on logout). */
fun reset() { _state.value = SessionState() }
/**
* Reset account-scoped state on logout, preserving device-scoped fields that are
* derived purely from feature flags via `observe(flag)`.
*
* Those flag observers are hot [StateFlow]s that only re-emit on a *value change*.
* A blanket `SessionState()` reset would clobber these fields to their defaults, and
* the observer would not re-push its unchanged current value to repopulate them —
* leaving the UI desynced from the still-persisted flag (e.g. Tipping flag on, but the
* scanner Tips tab gone). Account/token/settings-derived fields are safe to reset:
* their sources re-emit when the account changes, so they self-heal.
*/
fun reset() {
_state.update { prev ->
SessionState(
vibrateOnScan = prev.vibrateOnScan,
showNetworkOffline = prev.showNetworkOffline,
isTippingEnabled = prev.isTippingEnabled,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,28 @@ class SessionStateHolderTest {
}

@Test
fun `reset returns to default state`() {
fun `reset clears account-scoped state`() {
val holder = holder()
holder.update { it.copy(vibrateOnScan = true, hasGiveableBalance = true) }
holder.update { it.copy(hasGiveableBalance = true, contactDmUnreadCount = 3, isPhoneNumberSendEnabled = true) }
holder.reset()
assertEquals(SessionState(), holder.state.value)
val state = holder.state.value
assertEquals(false, state.hasGiveableBalance)
assertEquals(0, state.contactDmUnreadCount)
assertEquals(false, state.isPhoneNumberSendEnabled)
}

@Test
fun `reset preserves device-scoped feature-flag state`() {
// These are driven only by observe(flag) StateFlows, which won't re-emit an
// unchanged value to repopulate them after a blanket reset — so logout must
// keep them, or the UI desyncs from the still-persisted flag (e.g. Tips tab).
val holder = holder()
holder.update { it.copy(isTippingEnabled = true, vibrateOnScan = true, showNetworkOffline = true) }
holder.reset()
val state = holder.state.value
assertTrue(state.isTippingEnabled)
assertTrue(state.vibrateOnScan)
assertTrue(state.showNetworkOffline)
}

@Test
Expand Down
Loading