v1.0.59: pause/resume sync

New PAUSED status. When a sync is running, the sync button becomes a
pause button (⏸). Tapping it cancels the WorkManager job and sets the
status to PAUSED (purple). The button then becomes a play button (▶) to
resume. Works in both the home screen card and the pair detail screen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 01:51:45 +00:00
parent cb9fa1d3db
commit 21b7ffc7b3
6 changed files with 47 additions and 16 deletions
@@ -53,6 +53,7 @@ fun HomeScreen(
onClick = { onPairClick(pair.id) },
onSync = { vm.triggerSync(pair) },
onToggle = { vm.toggleEnabled(pair) },
onPause = { vm.pauseSync(pair) },
)
}
item { Spacer(Modifier.height(80.dp)) }
@@ -66,6 +67,7 @@ private fun SyncPairCard(
onClick: () -> Unit,
onSync: () -> Unit,
onToggle: () -> Unit,
onPause: () -> Unit = {},
) {
val accentColor = pair.lastSyncResult.accentColor
@@ -170,13 +172,16 @@ private fun SyncPairCard(
animationSpec = infiniteRepeatable(tween(900, easing = LinearEasing)),
label = "cardRotation",
)
FilledTonalIconButton(onClick = onSync, modifier = Modifier.size(36.dp)) {
Icon(
Icons.Default.Sync, "Sync now",
modifier = Modifier.size(18.dp).graphicsLayer {
if (pair.lastSyncResult == SyncStatus.SYNCING) rotationZ = syncRotation
},
)
when (pair.lastSyncResult) {
SyncStatus.SYNCING -> FilledTonalIconButton(onClick = onPause, modifier = Modifier.size(36.dp)) {
Icon(Icons.Default.Pause, "Pause sync", modifier = Modifier.size(18.dp))
}
SyncStatus.PAUSED -> FilledTonalIconButton(onClick = onSync, modifier = Modifier.size(36.dp)) {
Icon(Icons.Default.PlayArrow, "Resume sync", modifier = Modifier.size(18.dp))
}
else -> FilledTonalIconButton(onClick = onSync, modifier = Modifier.size(36.dp)) {
Icon(Icons.Default.Sync, "Sync now", modifier = Modifier.size(18.dp).graphicsLayer { rotationZ = syncRotation * 0f })
}
}
}
}
@@ -189,6 +194,7 @@ private fun StatusPill(status: SyncStatus) {
val (icon, label) = when (status) {
SyncStatus.SUCCESS -> Pair(Icons.Default.CheckCircle, "Synced")
SyncStatus.SYNCING -> Pair(Icons.Default.Sync, "Syncing…")
SyncStatus.PAUSED -> Pair(Icons.Default.Pause, "Paused")
SyncStatus.FAILED -> Pair(Icons.Default.Error, "Failed")
SyncStatus.CONFLICT -> Pair(Icons.Default.Warning, "Conflict")
SyncStatus.PARTIAL -> Pair(Icons.Default.WarningAmber, "Partial")
@@ -247,11 +253,12 @@ private fun EmptyState(modifier: Modifier = Modifier, onAdd: () -> Unit) {
private val SyncStatus.accentColor: Color
@Composable get() = when (this) {
SyncStatus.SUCCESS -> Color(0xFF2E7D32) // green — done, healthy
SyncStatus.SYNCING -> Color(0xFF1565C0) // blue — in progress
SyncStatus.FAILED -> Color(0xFFC62828) // red — error
SyncStatus.PARTIAL -> Color(0xFFE65100) // orange — some files failed
SyncStatus.CONFLICT -> Color(0xFFF9A825) // amber — needs resolution
SyncStatus.SUCCESS -> Color(0xFF2E7D32)
SyncStatus.SYNCING -> Color(0xFF1565C0)
SyncStatus.PAUSED -> Color(0xFF6A1B9A)
SyncStatus.FAILED -> Color(0xFFC62828)
SyncStatus.PARTIAL -> Color(0xFFE65100)
SyncStatus.CONFLICT -> Color(0xFFF9A825)
SyncStatus.IDLE -> MaterialTheme.colorScheme.outline
}