M12e: contract pass (pair invite kind, admin name in invite links, sync failed flag, rejection actor, device version name, template id, update check result, untouched accounts, D-027 re-stamp, PushEvent in :core:model, snackbar hosts)
Server: E-3 invite_kind; E-12 link ?from=<admin name>, E-4 passes it into kvitt://invite; X-Kvitt-App-Version-Name stored in devices.app_version_name (migration 0003, schema 3); OpenAPI and examples follow. Phone: :core:model, :core:fakes, :data, features and :app updated together; goldens re-recorded for S-20 (version name) and new S-1/S-22 states. 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
@@ -277,7 +277,7 @@ fun SyncPillSheet() {
|
||||
SyncStatusPill(Samples.pendingSummary, online = false, onClick = {})
|
||||
SyncStatusPill(Samples.rejectedSummary, online = true, onClick = {})
|
||||
SyncStatusPill(Samples.clockSummary, online = true, onClick = {})
|
||||
SyncStatusPill(Samples.failedSummary, online = true, onClick = {}, failed = true)
|
||||
SyncStatusPill(Samples.failedSummary, online = true, onClick = {})
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -94,12 +94,12 @@ object Samples {
|
||||
val gotlandDetail = AccountDetailState(gotland, BalanceView(0, anna), emptyList(), deletable = false, users = listOf(jacob, anna))
|
||||
val husetDetail = AccountDetailState(huset, BalanceView(-308_450, anna), emptyList(), deletable = false, users = listOf(jacob, anna))
|
||||
|
||||
val syncedSummary = SyncSummary("2026-10-17T11:59:00.000Z", pendingCount = 0, rejectedCount = 0, clockSkew = false, running = false)
|
||||
val syncedSummary = SyncSummary("2026-10-17T11:59:00.000Z", pendingCount = 0, rejectedCount = 0, clockSkew = false, running = false, lastAttemptFailed = false)
|
||||
val pendingSummary = syncedSummary.copy(pendingCount = 2)
|
||||
val runningSummary = syncedSummary.copy(running = true)
|
||||
val rejectedSummary = syncedSummary.copy(rejectedCount = 1)
|
||||
val clockSummary = syncedSummary.copy(clockSkew = true)
|
||||
val failedSummary = syncedSummary.copy(lastSuccessAt = "2026-10-17T09:30:00.000Z")
|
||||
val failedSummary = syncedSummary.copy(lastSuccessAt = "2026-10-17T09:30:00.000Z", lastAttemptFailed = true)
|
||||
|
||||
val update = UpdateInfo(versionCode = 12, versionName = "1.2", sizeBytes = 8_400_000)
|
||||
|
||||
|
||||
@@ -47,14 +47,14 @@ import kotlin.time.Instant
|
||||
enum class SyncPillVariant { Synced, Running, Offline, Rejected, ClockWrong, Failed }
|
||||
|
||||
/**
|
||||
* The pill's variant: the clock problem and refused changes win, then offline, a failed last attempt ([failed]: the
|
||||
* repositories do not expose it yet, see the M6 report), running, else synced.
|
||||
* The pill's variant: the clock problem and refused changes win, then offline, a failed last attempt
|
||||
* (`SyncSummary.lastAttemptFailed`), running, else synced.
|
||||
*/
|
||||
fun syncPillVariant(summary: SyncSummary, online: Boolean, failed: Boolean = false): SyncPillVariant = when {
|
||||
fun syncPillVariant(summary: SyncSummary, online: Boolean): SyncPillVariant = when {
|
||||
summary.clockSkew -> SyncPillVariant.ClockWrong
|
||||
summary.rejectedCount > 0 -> SyncPillVariant.Rejected
|
||||
!online -> SyncPillVariant.Offline
|
||||
failed -> SyncPillVariant.Failed
|
||||
summary.lastAttemptFailed -> SyncPillVariant.Failed
|
||||
summary.running || summary.lastSuccessAt == null -> SyncPillVariant.Running
|
||||
else -> SyncPillVariant.Synced
|
||||
}
|
||||
@@ -75,10 +75,10 @@ fun relativeTime(rfc3339: String): String {
|
||||
/** The visible text of the sync line, also used by S-13's "Synkstatus" subtitle. */
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun syncLineText(summary: SyncSummary, online: Boolean, failed: Boolean = false): String {
|
||||
fun syncLineText(summary: SyncSummary, online: Boolean): String {
|
||||
val dot = stringResource(R.string.common_separator_dot)
|
||||
val pending = if (summary.pendingCount > 0) pluralStringResource(R.plurals.sync_pending, summary.pendingCount, summary.pendingCount) else null
|
||||
return when (syncPillVariant(summary, online, failed)) {
|
||||
return when (syncPillVariant(summary, online)) {
|
||||
SyncPillVariant.ClockWrong -> stringResource(R.string.sync_clock_wrong_short)
|
||||
SyncPillVariant.Rejected -> pluralStringResource(R.plurals.sync_rejected, summary.rejectedCount, summary.rejectedCount)
|
||||
SyncPillVariant.Offline -> listOfNotNull(stringResource(R.string.common_offline), pending).joinToString(dot)
|
||||
@@ -93,11 +93,11 @@ fun syncLineText(summary: SyncSummary, online: Boolean, failed: Boolean = false)
|
||||
* The caller refreshes it every minute by recomposing with the clock.
|
||||
*/
|
||||
@Composable
|
||||
fun SyncStatusPill(summary: SyncSummary, online: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, failed: Boolean = false) {
|
||||
fun SyncStatusPill(summary: SyncSummary, online: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val colors = KvittTheme.colors
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val variant = syncPillVariant(summary, online, failed)
|
||||
val text = syncLineText(summary, online, failed)
|
||||
val variant = syncPillVariant(summary, online)
|
||||
val text = syncLineText(summary, online)
|
||||
val (fill, icon, iconTint) = when (variant) {
|
||||
SyncPillVariant.Synced -> Triple(scheme.surfaceContainerHigh, KvittIcon.CloudDone, colors.owed)
|
||||
SyncPillVariant.Running -> Triple(scheme.surfaceContainerHigh, KvittIcon.Sync, scheme.onSurfaceVariant)
|
||||
|
||||
+2
-2
@@ -97,13 +97,13 @@ class ComponentLogicTest {
|
||||
|
||||
@Test
|
||||
fun syncPillVariants() {
|
||||
val synced = SyncSummary("2026-10-17T11:59:00.000Z", 0, 0, clockSkew = false, running = false)
|
||||
val synced = SyncSummary("2026-10-17T11:59:00.000Z", 0, 0, clockSkew = false, running = false, lastAttemptFailed = false)
|
||||
assertEquals(SyncPillVariant.Synced, syncPillVariant(synced, online = true))
|
||||
assertEquals(SyncPillVariant.Running, syncPillVariant(synced.copy(running = true), online = true))
|
||||
assertEquals(SyncPillVariant.Offline, syncPillVariant(synced.copy(pendingCount = 2), online = false))
|
||||
assertEquals(SyncPillVariant.Rejected, syncPillVariant(synced.copy(rejectedCount = 1), online = false))
|
||||
assertEquals(SyncPillVariant.ClockWrong, syncPillVariant(synced.copy(clockSkew = true, rejectedCount = 1), online = true))
|
||||
assertEquals(SyncPillVariant.Failed, syncPillVariant(synced, online = true, failed = true))
|
||||
assertEquals(SyncPillVariant.Failed, syncPillVariant(synced.copy(lastAttemptFailed = true), online = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user