Android overhaul: shared failure texts, saved-split label and limits in :core:design
* format/Failures.kt: FAILURE_NETWORK and commonFailureText, the §14.3.2 code-to-text map that :feature:account
(Errors.kt) and :feature:settings (Messages.kt) each spelled out; both keep only their own codes. The "network"
literals in :feature:onboarding and the two NETWORK constants use FAILURE_NETWORK.
* component/Split.kt: savedSplitLabel, the S-8 and S-16 line ("Kontots standard 50/50", "Egen fördelning 70/30",
"Exakta belopp", "Allt på dig/Anna") that ExpenseDetailScreen.modeLabel and SyncStatusScreen.lostModeLabel
duplicated line for line.
* format/Limits.kt: BP_TOTAL (five copies in four features and a literal in :core:design and S-19) and
DISPLAY_NAME_MAX (three copies in :feature:onboarding and :feature:settings).
Behaviour and goldens unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
+1
-1
@@ -45,6 +45,7 @@ import se.hagstedt.kvitt.design.component.TopBarNavigation
|
||||
import se.hagstedt.kvitt.design.component.kvittLanguage
|
||||
import se.hagstedt.kvitt.design.component.ratioSpoken
|
||||
import se.hagstedt.kvitt.design.component.showKvittSnackbar
|
||||
import se.hagstedt.kvitt.design.format.BP_TOTAL
|
||||
import se.hagstedt.kvitt.design.format.MoneyFormatter
|
||||
import se.hagstedt.kvitt.design.icon.KvittIcon
|
||||
import se.hagstedt.kvitt.model.AccountId
|
||||
@@ -215,7 +216,6 @@ private fun defaultSplit(ui: AccountDetailUi.Ready): String {
|
||||
return stringResource(R.string.account_default_split, ratio, names)
|
||||
}
|
||||
|
||||
internal const val BP_TOTAL = 10_000
|
||||
|
||||
/** `more_vert` with the S-5 menu; disabled items carry their reason as a second line (design-spec §3.11). */
|
||||
@Composable
|
||||
|
||||
+2
-4
@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import se.hagstedt.kvitt.design.format.FAILURE_NETWORK
|
||||
import se.hagstedt.kvitt.model.AccountDetailState
|
||||
import se.hagstedt.kvitt.model.AccountId
|
||||
import se.hagstedt.kvitt.model.AccountRepository
|
||||
@@ -170,10 +171,7 @@ class AccountDetailViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun failureMessage(code: String, action: AccountAction): AccountMessage = when (code) {
|
||||
NETWORK -> AccountMessage.CouldNotReach(action)
|
||||
FAILURE_NETWORK -> AccountMessage.CouldNotReach(action)
|
||||
"account_not_empty" -> AccountMessage.DeleteBlocked
|
||||
else -> AccountMessage.Failed(code)
|
||||
}
|
||||
|
||||
/** `OnlineResult.Failed` code of a network failure (design-spec §1, §9.9). */
|
||||
internal const val NETWORK = "network"
|
||||
|
||||
+1
@@ -54,6 +54,7 @@ import se.hagstedt.kvitt.design.component.kvittLanguage
|
||||
import se.hagstedt.kvitt.design.component.money
|
||||
import se.hagstedt.kvitt.design.component.ratioSpoken
|
||||
import se.hagstedt.kvitt.design.component.showKvittSnackbar
|
||||
import se.hagstedt.kvitt.design.format.BP_TOTAL
|
||||
import se.hagstedt.kvitt.design.format.MoneyFormatter
|
||||
import se.hagstedt.kvitt.design.icon.KvittIcon
|
||||
import se.hagstedt.kvitt.design.theme.KvittDimens
|
||||
|
||||
+3
-1
@@ -13,6 +13,8 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import se.hagstedt.kvitt.design.format.BP_TOTAL
|
||||
import se.hagstedt.kvitt.design.format.FAILURE_NETWORK
|
||||
import se.hagstedt.kvitt.model.AccountId
|
||||
import se.hagstedt.kvitt.model.AccountRepository
|
||||
import se.hagstedt.kvitt.model.ConnectivityRepository
|
||||
@@ -183,7 +185,7 @@ private fun AccountEditorUi.failed(code: String, field: String?): AccountEditorU
|
||||
code == "name_taken" -> copy(saving = false, serverNameError = NameError.Taken(name.trim()))
|
||||
code == "validation_failed" && field == "name" -> copy(saving = false, serverNameError = NameError.Invalid)
|
||||
code == "validation_failed" && field == "default_shares" -> copy(saving = false, splitError = true)
|
||||
code == NETWORK -> copy(saving = false, message = EditorMessage.CouldNotReach)
|
||||
code == FAILURE_NETWORK -> copy(saving = false, message = EditorMessage.CouldNotReach)
|
||||
else -> copy(saving = false, message = EditorMessage.Failed(code))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
package se.hagstedt.kvitt.feature.account
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import se.hagstedt.kvitt.design.format.commonFailureText
|
||||
import se.hagstedt.kvitt.core.design.R as DesignR
|
||||
|
||||
/** The snackbar text of an `OnlineResult.Failed` code without a field at fault (§14.3.2, strings.csv `error_*`). */
|
||||
@StringRes
|
||||
internal fun errorText(code: String): Int = when (code) {
|
||||
NETWORK -> DesignR.string.common_could_not_reach_server
|
||||
"household_incomplete" -> DesignR.string.error_household_incomplete
|
||||
"not_found" -> DesignR.string.error_not_found
|
||||
"forbidden" -> DesignR.string.error_forbidden
|
||||
"rate_limited" -> DesignR.string.error_rate_limited
|
||||
"protocol_unsupported" -> DesignR.string.error_app_too_old
|
||||
"internal", "unavailable", "not_implemented" -> DesignR.string.error_server
|
||||
"validation_failed" -> DesignR.string.error_validation
|
||||
"account_not_empty" -> R.string.account_delete_blocked
|
||||
else -> DesignR.string.error_generic_invalid
|
||||
else -> commonFailureText(code) ?: DesignR.string.error_generic_invalid
|
||||
}
|
||||
|
||||
+3
-2
@@ -8,6 +8,7 @@ import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
import se.hagstedt.kvitt.design.format.FAILURE_NETWORK
|
||||
import se.hagstedt.kvitt.fakes.FakeScenario
|
||||
import se.hagstedt.kvitt.fakes.account.AccountScenarios
|
||||
import se.hagstedt.kvitt.model.AccountId
|
||||
@@ -28,7 +29,7 @@ val accountStates: List<ScreenState> = listOf(
|
||||
detail("S-5-account-delete-confirm", AccountScenarios.empty, AccountScenarios.sommarstugan.id) { it.askDelete() },
|
||||
detail("S-5-account-empty", AccountScenarios.empty, AccountScenarios.sommarstugan.id),
|
||||
detail("S-5-account-offline", AccountScenarios.offline, AccountScenarios.vardag) { it.setMenuOpen(true) },
|
||||
detail("S-5-account-error", AccountScenarios.loaded, AccountScenarios.vardag, failWith = NETWORK) { it.archive() },
|
||||
detail("S-5-account-error", AccountScenarios.loaded, AccountScenarios.vardag, failWith = FAILURE_NETWORK) { it.archive() },
|
||||
detail("S-5-account-long", AccountScenarios.long, AccountScenarios.longAccount, largeFont = true),
|
||||
editor("S-6-account-editor-create", AccountScenarios.editor),
|
||||
editor("S-6-account-editor", AccountScenarios.editor, largeFont = true) { it.onName("Sommarstugan"); it.onViewerPercent(60) },
|
||||
@@ -36,7 +37,7 @@ val accountStates: List<ScreenState> = listOf(
|
||||
editor("S-6-account-editor-decimals", AccountScenarios.editor) { it.onName("Sommarstugan"); it.onPercent(PercentField.Viewer, "62,75") },
|
||||
editor("S-6-account-editor-error", AccountScenarios.editor) { it.onName("Vardag"); it.save() },
|
||||
editor("S-6-account-editor-validation", AccountScenarios.editor) { it.onName(LONG_NAME) },
|
||||
editor("S-6-account-editor-network", AccountScenarios.editor, failWith = NETWORK) { it.onName("Sommarstugan"); it.save() },
|
||||
editor("S-6-account-editor-network", AccountScenarios.editor, failWith = FAILURE_NETWORK) { it.onName("Sommarstugan"); it.save() },
|
||||
editor("S-6-account-editor-offline", AccountScenarios.editorOffline) { it.onName("Sommarstugan") },
|
||||
saving(),
|
||||
)
|
||||
|
||||
+4
-3
@@ -5,6 +5,7 @@ import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import se.hagstedt.kvitt.design.format.FAILURE_NETWORK
|
||||
import se.hagstedt.kvitt.fakes.FakeRepositories
|
||||
import se.hagstedt.kvitt.fakes.FakeScenarios
|
||||
import se.hagstedt.kvitt.fakes.account.AccountScenarios
|
||||
@@ -65,11 +66,11 @@ class AccountDetailViewModelTest {
|
||||
|
||||
@Test
|
||||
fun networkFailureOffersRetryAndChangesNothing() {
|
||||
val ui = ready(detailState(AccountScenarios.loaded, AccountScenarios.vardag, failWith = NETWORK) { it.archive() })
|
||||
val ui = ready(detailState(AccountScenarios.loaded, AccountScenarios.vardag, failWith = FAILURE_NETWORK) { it.archive() })
|
||||
assertEquals(AccountMessage.CouldNotReach(AccountAction.Archive), ui.message)
|
||||
assertFalse(ui.detail.account.archived)
|
||||
assertFalse(ui.busy)
|
||||
val shown = ready(detailState(AccountScenarios.loaded, AccountScenarios.vardag, failWith = NETWORK) { it.archive(); it.messageShown() })
|
||||
val shown = ready(detailState(AccountScenarios.loaded, AccountScenarios.vardag, failWith = FAILURE_NETWORK) { it.archive(); it.messageShown() })
|
||||
assertNull(shown.message)
|
||||
}
|
||||
|
||||
@@ -190,7 +191,7 @@ class AccountEditorViewModelTest {
|
||||
fun serverValidationMapsToTheField() {
|
||||
assertEquals(NameError.Invalid, editorState(AccountScenarios.editor, failWith = "validation_failed", field = "name") { it.onName("X"); it.save() }.nameError)
|
||||
assertTrue(editorState(AccountScenarios.editor, failWith = "validation_failed", field = "default_shares") { it.onName("X"); it.save() }.splitError)
|
||||
assertEquals(EditorMessage.CouldNotReach, editorState(AccountScenarios.editor, failWith = NETWORK) { it.onName("X"); it.save() }.message)
|
||||
assertEquals(EditorMessage.CouldNotReach, editorState(AccountScenarios.editor, failWith = FAILURE_NETWORK) { it.onName("X"); it.save() }.message)
|
||||
assertEquals(EditorMessage.Failed("household_incomplete"), editorState(AccountScenarios.editor, failWith = "household_incomplete") { it.onName("X"); it.save() }.message)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user