feat: fix sync counters, polished activity rows, Files tab, new icon
- Fix SYNC_COMPLETED showing ↑0 ↓0 ✗0 when only deletions occurred: add ✕N for deleted files to the summary message (↑N ↓N ✕N ✗N format) - Fix PairDetail Activity section showing raw "SYNC_STARTED" enum names and "remote" as a plain subtitle: replace dot-based EventRow with the same polished icon-bubble rows as the global Log tab - Extract shared SyncEventRow composable + iconAndTint/label helpers to ui/shared/SyncEventRow.kt; both LogScreen and PairDetailScreen now use it - Add Files tab (4th tab between Log and Accounts): folder browser showing all synced files per pair, grouped by subdirectory, with file-type icons, size, last-synced date, and a summary header (N files, total size) - Add SyncFileStateDao.observeForPair() reactive Flow query for Files tab - Completely redesign app icon: near-black radial gradient background with three bold directional arrows in an S-pattern (coral → silver → teal), each with gradient fills and tip-glow dots — entirely different from the typical circular sync-arrow style - Bump version to 1.0.22 (build 23) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,14 +17,12 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.syncflow.data.db.entities.SyncEventEntity
|
||||
import com.syncflow.data.db.entities.SyncPairEntity
|
||||
import com.syncflow.domain.model.SyncEventType
|
||||
import com.syncflow.domain.model.SyncStatus
|
||||
import com.syncflow.ui.shared.SyncEventRow
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
@@ -132,7 +130,7 @@ fun PairDetailScreen(
|
||||
}
|
||||
} else {
|
||||
items(events, key = { it.id }) { event ->
|
||||
EventRow(event)
|
||||
SyncEventRow(event, showDivider = event != events.last())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,56 +229,6 @@ private fun InfoRow(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EventRow(event: SyncEventEntity) {
|
||||
val fmt = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
val zone = ZoneId.systemDefault()
|
||||
val dotColor = eventColor(event.eventType)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// Colored dot indicator
|
||||
Surface(
|
||||
shape = RoundedCornerShape(50),
|
||||
color = dotColor,
|
||||
modifier = Modifier.size(8.dp),
|
||||
) {}
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
event.filePath ?: event.message ?: event.eventType.name,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
event.message?.takeIf { event.filePath != null }?.let {
|
||||
Text(
|
||||
it,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
fmt.format(event.timestamp.atZone(zone)),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun eventColor(type: SyncEventType): Color = when (type) {
|
||||
SyncEventType.SYNC_STARTED -> MaterialTheme.colorScheme.primary
|
||||
SyncEventType.SYNC_COMPLETED -> MaterialTheme.colorScheme.primary
|
||||
SyncEventType.SYNC_FAILED -> MaterialTheme.colorScheme.error
|
||||
SyncEventType.FILE_UPLOADED -> MaterialTheme.colorScheme.secondary
|
||||
SyncEventType.FILE_DOWNLOADED -> MaterialTheme.colorScheme.secondary
|
||||
SyncEventType.FILE_DELETED -> MaterialTheme.colorScheme.tertiary
|
||||
SyncEventType.FILE_SKIPPED -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||
SyncEventType.CONFLICT_DETECTED -> MaterialTheme.colorScheme.tertiary
|
||||
SyncEventType.CONFLICT_RESOLVED -> MaterialTheme.colorScheme.primary
|
||||
}
|
||||
|
||||
private fun String.toDisplayPath(): String {
|
||||
val decoded = java.net.URLDecoder.decode(this, "UTF-8")
|
||||
return decoded.trimEnd('/').substringAfterLast('/').substringAfterLast(':').ifEmpty { decoded }
|
||||
|
||||
Reference in New Issue
Block a user