M7g: feature cleanups after the design-system fix pass
Resource-backed snackbars (showKvittSnackbar) in account, onboarding and settings; SettingsScaffold replaced by KvittScaffold + SettingsMessageEffect. KvittButton(loading) for S-6 saving and S-16 Synka nu. S-10 missing state disables Synka nu offline (connectivity in the ViewModel, new detail_missing_offline goldens). S-6 system back asks to discard unsaved changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
+2
-1
@@ -123,7 +123,8 @@ fun SettlementDetailContent(
|
||||
action = stringResource(R.string.settle_detail_sync_now),
|
||||
actionIcon = KvittIcon.Sync,
|
||||
actionKind = ButtonKind.Tonal,
|
||||
onAction = actions::onSyncNow,
|
||||
actionEnabled = state.online,
|
||||
onAction = { if (state.online) actions.onSyncNow() },
|
||||
)
|
||||
else -> DetailBody(state, detail)
|
||||
}
|
||||
|
||||
+8
-3
@@ -16,6 +16,7 @@ import se.hagstedt.kvitt.model.Account
|
||||
import se.hagstedt.kvitt.model.AccountId
|
||||
import se.hagstedt.kvitt.model.AccountRepository
|
||||
import se.hagstedt.kvitt.model.AuditAction
|
||||
import se.hagstedt.kvitt.model.ConnectivityRepository
|
||||
import se.hagstedt.kvitt.model.HomeRepository
|
||||
import se.hagstedt.kvitt.model.SettlementDetailState
|
||||
import se.hagstedt.kvitt.model.SettlementId
|
||||
@@ -25,7 +26,8 @@ import se.hagstedt.kvitt.model.TimelineLine
|
||||
import se.hagstedt.kvitt.model.User
|
||||
|
||||
/**
|
||||
* Everything S-10 shows. [detail] null after loading is the missing state (the S-8 missing layout, design-spec S-10).
|
||||
* Everything S-10 shows. [detail] null after loading is the missing state (the S-8 missing layout, design-spec S-10);
|
||||
* its "Synka nu" needs [online].
|
||||
*/
|
||||
data class SettlementDetailUiState(
|
||||
val loading: Boolean = true,
|
||||
@@ -35,6 +37,7 @@ data class SettlementDetailUiState(
|
||||
val accounts: List<Account> = emptyList(),
|
||||
val confirmDelete: Boolean = false,
|
||||
val deletedByMe: Boolean = false,
|
||||
val online: Boolean = true,
|
||||
) {
|
||||
/** Design-spec §2.4: an account's colour is its position in `AccountRepository.all()`. */
|
||||
fun colorIndex(id: AccountId): Int = accounts.indexOfFirst { it.id == id }.coerceAtLeast(0)
|
||||
@@ -61,6 +64,7 @@ class SettlementDetailViewModel @javax.inject.Inject constructor(
|
||||
private val settlements: SettlementRepository,
|
||||
accounts: AccountRepository,
|
||||
home: HomeRepository,
|
||||
connectivity: ConnectivityRepository,
|
||||
private val sync: SyncRepository,
|
||||
) : ViewModel(), SettlementDetailActions {
|
||||
val id: SettlementId? = savedStateHandle.get<String>(SettleArgs.SETTLEMENT_ID)?.let(::SettlementId)
|
||||
@@ -70,9 +74,10 @@ class SettlementDetailViewModel @javax.inject.Inject constructor(
|
||||
id?.let { settlements.detail(it) } ?: flowOf(null),
|
||||
accounts.all(),
|
||||
home.home(),
|
||||
connectivity.online,
|
||||
local,
|
||||
) { detail, all, h, l ->
|
||||
l.copy(loading = false, detail = detail, accounts = all, me = h.me, partner = h.partner)
|
||||
) { detail, all, h, online, l ->
|
||||
l.copy(loading = false, detail = detail, accounts = all, me = h.me, partner = h.partner, online = online)
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(STOP_TIMEOUT_MS), SettlementDetailUiState())
|
||||
|
||||
override fun onDelete() = local.update { it.copy(confirmDelete = true) }
|
||||
|
||||
+9
-1
@@ -30,7 +30,7 @@ class SettlementDetailViewModelTest {
|
||||
fun reset() = Dispatchers.resetMain()
|
||||
|
||||
private fun vm(id: String, repos: FakeRepositories = FakeRepositories(SettleScenarios.detail)) =
|
||||
SettlementDetailViewModel(SavedStateHandle(mapOf(SettleArgs.SETTLEMENT_ID to id)), repos.settlements, repos.accounts, repos.home, repos.sync)
|
||||
SettlementDetailViewModel(SavedStateHandle(mapOf(SettleArgs.SETTLEMENT_ID to id)), repos.settlements, repos.accounts, repos.home, repos.connectivity, repos.sync)
|
||||
|
||||
@Test
|
||||
fun loadedShowsTheSettlementAndItsActions() = runTest {
|
||||
@@ -48,6 +48,14 @@ class SettlementDetailViewModelTest {
|
||||
assertFalse(s.actions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingOfflineCarriesTheConnectivityForSyncNow() = runTest {
|
||||
assertTrue(vm(SettleScenarios.missing.value).state.first { !it.loading }.online)
|
||||
val offline = vm(SettleScenarios.missing.value, FakeRepositories(SettleScenarios.detailOffline)).state.first { !it.loading }
|
||||
assertNull(offline.detail)
|
||||
assertFalse(offline.online)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletedHasTheDeletionLineAndNoActions() = runTest {
|
||||
val s = vm(SettleScenarios.deleted.value).state.first { !it.loading }
|
||||
|
||||
+2
-1
@@ -78,6 +78,7 @@ private val cases: Map<String, Case> = linkedMapOf(
|
||||
"detail_offline" to Detail(SettleScenarios.detailOffline, SettleScenarios.split.value),
|
||||
"detail_delete_dialog" to Detail(SettleScenarios.detail, SettleScenarios.swish.value) { onDelete() },
|
||||
"detail_missing" to Detail(SettleScenarios.detail, SettleScenarios.missing.value),
|
||||
"detail_missing_offline" to Detail(SettleScenarios.detailOffline, SettleScenarios.missing.value),
|
||||
"detail_long" to Detail(SettleScenarios.longDetail, SettleScenarios.long.value, height = longHeight),
|
||||
)
|
||||
|
||||
@@ -105,7 +106,7 @@ class SettleScreenshotTest(private val name: String) {
|
||||
is Detail -> {
|
||||
val repos = FakeRepositories(case.scenario)
|
||||
val vm = SettlementDetailViewModel(
|
||||
SavedStateHandle(mapOf(SettleArgs.SETTLEMENT_ID to case.id)), repos.settlements, repos.accounts, repos.home, repos.sync,
|
||||
SavedStateHandle(mapOf(SettleArgs.SETTLEMENT_ID to case.id)), repos.settlements, repos.accounts, repos.home, repos.connectivity, repos.sync,
|
||||
)
|
||||
vm.apply(case.steps)
|
||||
ScreenSnapshots.snap(compose, name, case.height, case.largeFont) {
|
||||
|
||||
Reference in New Issue
Block a user