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
@@ -17,32 +17,41 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.window.Dialog
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.datetime.toLocalDateTime
import se.hagstedt.kvitt.design.KvittElevation
import se.hagstedt.kvitt.design.KvittTheme
import se.hagstedt.kvitt.design.component.Banner
import se.hagstedt.kvitt.design.component.BannerKind
import se.hagstedt.kvitt.design.component.ButtonKind
import se.hagstedt.kvitt.design.component.ButtonSize
import se.hagstedt.kvitt.design.component.ExpenseRow
import se.hagstedt.kvitt.design.component.FactRow
import se.hagstedt.kvitt.design.component.KvittBadge
import se.hagstedt.kvitt.design.component.KvittButton
import se.hagstedt.kvitt.design.component.KvittCard
import se.hagstedt.kvitt.design.component.KvittIconImage
@@ -53,35 +62,23 @@ import se.hagstedt.kvitt.design.component.NeedsConnectionHint
import se.hagstedt.kvitt.design.component.ProvideHousehold
import se.hagstedt.kvitt.design.component.SectionHeader
import se.hagstedt.kvitt.design.component.SegmentedGroup
import se.hagstedt.kvitt.design.component.SegmentedItemScope
import se.hagstedt.kvitt.design.component.SettlementRow
import se.hagstedt.kvitt.design.component.TopBarNavigation
import se.hagstedt.kvitt.design.component.kvittLanguage
import se.hagstedt.kvitt.design.component.personName
import se.hagstedt.kvitt.design.component.relativeTime
import se.hagstedt.kvitt.design.component.savedSplitLabel
import se.hagstedt.kvitt.design.format.DateFormatter
import se.hagstedt.kvitt.design.icon.KvittIcon
import se.hagstedt.kvitt.design.theme.KvittDimens
import se.hagstedt.kvitt.model.EntityType
import se.hagstedt.kvitt.model.ExpenseId
import se.hagstedt.kvitt.model.LedgerRow
import se.hagstedt.kvitt.model.RejectedVersion
import se.hagstedt.kvitt.model.Rejection
import se.hagstedt.kvitt.model.SettlementId
import se.hagstedt.kvitt.model.SyncRejectionReason
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Surface
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.window.Dialog
import se.hagstedt.kvitt.design.component.FactRow
import se.hagstedt.kvitt.design.component.KvittBadge
import se.hagstedt.kvitt.design.component.SegmentedItemScope
import se.hagstedt.kvitt.design.component.defaultModeLabel
import se.hagstedt.kvitt.design.component.personName
import se.hagstedt.kvitt.design.component.splitModeLabel
import se.hagstedt.kvitt.design.format.MoneyFormatter
import se.hagstedt.kvitt.design.KvittElevation
import se.hagstedt.kvitt.model.RejectedVersion
import se.hagstedt.kvitt.model.SplitMode
import kotlin.time.Instant
import se.hagstedt.kvitt.core.design.R as DesignR
@@ -383,7 +380,7 @@ private fun LostFacts(lost: RejectedVersion, ui: SyncStatusUi.Ready, modifier: M
add { item -> ExpenseRow(item, row, onClick = null) }
lost.accountName?.let { name -> add { item -> FactRow(item, KvittIcon.Inventory2, stringResource(DesignR.string.editor_account), name) } }
lost.payer?.let { payer -> add { item -> FactRow(item, KvittIcon.Person, stringResource(DesignR.string.editor_paid_by), personName(payer)) } }
lost.mode?.let { mode -> add { item -> FactRow(item, KvittIcon.CallSplit, stringResource(DesignR.string.editor_split), lostModeLabel(mode, lost, ui)) } }
lost.mode?.let { mode -> add { item -> FactRow(item, KvittIcon.CallSplit, stringResource(DesignR.string.editor_split), savedSplitLabel(mode, lost.shares, ui.me.id, ui.partner?.displayName.orEmpty())) } }
if (lost.notes.isNotEmpty()) add { item -> FactRow(item, KvittIcon.Notes, stringResource(DesignR.string.editor_notes), lost.notes) }
}
is LedgerRow.SettlementRow -> {
@@ -394,23 +391,3 @@ private fun LostFacts(lost: RejectedVersion, ui: SyncStatusUi.Ready, modifier: M
}
SegmentedGroup(facts.size, modifier) { item -> facts[item.index](item) }
}
/** "Kontots standard 50/50" / "Egen fördelning 70/30" / "Exakta belopp" / "Allt på Anna", as on S-8. */
@Composable
private fun lostModeLabel(mode: SplitMode, lost: RejectedVersion, ui: SyncStatusUi.Ready): String {
val language = kvittLanguage()
val mineBp = lost.shares.firstOrNull { it.userId == ui.me.id }?.shareBp
val ratio = mineBp?.let { stringResource(DesignR.string.common_ratio, MoneyFormatter.basisPoints(it, language), MoneyFormatter.basisPoints(BP_TOTAL - it, language)) }
return when (mode) {
SplitMode.DEFAULT -> if (ratio != null) stringResource(DesignR.string.editor_split_mode_default, ratio) else splitModeLabel(mode, defaultModeLabel(HALF_PERCENT))
SplitMode.RATIO -> splitModeLabel(mode, "") + (ratio?.let { " $it" } ?: "")
SplitMode.EXACT -> splitModeLabel(mode, "")
SplitMode.FULL -> {
val bearer = lost.shares.firstOrNull { it.shareOre > 0 }?.userId
if (bearer == ui.me.id) stringResource(DesignR.string.editor_split_full_on_you) else stringResource(DesignR.string.editor_split_full_on, ui.partner?.displayName.orEmpty())
}
}
}
private const val BP_TOTAL = 10_000
private const val HALF_PERCENT = 50