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:
2026-10-18 23:04:30 +02:00
parent 8229ed60ea
commit 515b60fc65
24 changed files with 132 additions and 128 deletions
@@ -48,6 +48,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import se.hagstedt.kvitt.core.design.R
import se.hagstedt.kvitt.design.KvittTheme
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
@@ -113,7 +114,7 @@ fun ratioSpoken(left: User, leftBp: Int, right: User): String {
personName(left),
MoneyFormatter.basisPoints(leftBp, language),
personName(right),
MoneyFormatter.basisPoints(10_000 - leftBp, language),
MoneyFormatter.basisPoints(BP_TOTAL - leftBp, language),
)
}
@@ -202,6 +203,29 @@ fun splitModeLabel(mode: SplitMode, defaultLabel: String): String = when (mode)
fun defaultModeLabel(viewerPercent: Int): String =
stringResource(R.string.editor_split_mode_default, stringResource(R.string.common_ratio, viewerPercent.toString(), (100 - viewerPercent).toString()))
/**
* A saved split as one line (design-spec S-8, S-16): "Kontots standard 50/50" or "Egen fördelning 70/30" with the
* [viewer]'s basis points from [shares] (50/50 for a default split without them), "Exakta belopp", or "Allt på dig" /
* "Allt på <[partnerName]>" by who bears the amount.
*/
@Composable
fun savedSplitLabel(mode: SplitMode, shares: List<Share>, viewer: UserId, partnerName: String): String {
val language = kvittLanguage()
val mineBp = shares.firstOrNull { it.userId == viewer }?.shareBp
val ratio = mineBp?.let { stringResource(R.string.common_ratio, MoneyFormatter.basisPoints(it, language), MoneyFormatter.basisPoints(BP_TOTAL - it, language)) }
return when (mode) {
SplitMode.DEFAULT -> if (ratio != null) stringResource(R.string.editor_split_mode_default, ratio) else defaultModeLabel(HALF_PERCENT)
SplitMode.RATIO -> splitModeLabel(mode, "") + (ratio?.let { " $it" } ?: "")
SplitMode.EXACT -> splitModeLabel(mode, "")
SplitMode.FULL -> {
val bearer = shares.firstOrNull { it.shareOre > 0 }?.userId
if (bearer == viewer) stringResource(R.string.editor_split_full_on_you) else stringResource(R.string.editor_split_full_on, partnerName)
}
}
}
private const val HALF_PERCENT = 50
/**
* The open split section (design-spec §3.7): a flat section with the header "Delning" and a collapse button, the four
* mode chips as a radio group, then [body]: the split bar and preview for `default`, or [RatioEditor],
@@ -474,4 +498,3 @@ fun viewerPercentOf(defaultShares: Map<UserId, Int>, viewer: UserId): Int = (def
private const val HALF_BP = 5_000
private const val BP_PER_PERCENT = 100
@@ -0,0 +1,25 @@
package se.hagstedt.kvitt.design.format
import androidx.annotation.StringRes
import se.hagstedt.kvitt.core.design.R
/** The code of an online call that did not reach the server: `OnlineResult.Failed("network", null)` (D-029). */
const val FAILURE_NETWORK = "network"
/**
* The shared snackbar text of a refused online call (design-spec §1, `strings.csv` `error_*`) for the codes every
* screen can meet: [FAILURE_NETWORK] is "Kunde inte nå servern", the §14.3.2 codes their text. Null for any other code,
* which the screen maps itself (falling back to `error_generic_invalid`).
*/
@StringRes
fun commonFailureText(code: String): Int? = when (code) {
FAILURE_NETWORK -> R.string.common_could_not_reach_server
"household_incomplete" -> R.string.error_household_incomplete
"not_found" -> R.string.error_not_found
"forbidden" -> R.string.error_forbidden
"rate_limited" -> R.string.error_rate_limited
"protocol_unsupported" -> R.string.error_app_too_old
"internal", "unavailable", "not_implemented" -> R.string.error_server
"validation_failed" -> R.string.error_validation
else -> null
}
@@ -0,0 +1,7 @@
package se.hagstedt.kvitt.design.format
/** Basis points of a whole: a ratio's per-user shares sum to 10000 bp, 100 % (SPEC §1, §9.3). */
const val BP_TOTAL = 10_000
/** A user's display name is 1 to 40 code points after trimming (SPEC §9.6). */
const val DISPLAY_NAME_MAX = 40