Fix verification messaging: distinguish three copy outcomes

Three states:
1. SHA-256 verified (green) — remote hash matched dest hash
2. Copied, no remote checksum (secondary color) — file copied OK,
   device doesn't support sha256sum (Samsung/toybox)
3. Failed (red) — copy exception or hash mismatch

Previously all non-checksum cases showed 'may be corrupt' which
was a false alarm on devices without sha256sum support.
This commit is contained in:
Kyle Bolen
2026-07-28 05:42:51 +00:00
parent 0be43f2236
commit 3c59638759
2 changed files with 31 additions and 14 deletions

View File

@@ -143,7 +143,11 @@ class Copier(
destSha256 = destHash,
verified = verified,
convertedFromHeic = wasConverted,
error = if (!verified) "Hash mismatch — file may be corrupt" else null,
error = when {
!verified && remoteHash != null -> "Hash mismatch — file may be corrupt"
!verified -> "Copy failed — file missing or empty"
else -> null
},
)
} catch (e: Exception) {
pulledFile?.deleteIfExists()

View File

@@ -224,16 +224,27 @@ private fun ResultSummaryHeader(success: Int, failure: Int, destination: String)
@Composable
private fun CopyResultRow(result: CopyResult) {
val hasRemoteHash = result.sourceSha256 != "(not available)"
val isVerifiedWithHash = result.verified && result.error == null && hasRemoteHash
val isVerifiedNoHash = result.verified && result.error == null && !hasRemoteHash
val isFailed = result.error != null || !result.verified
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
if (result.verified && result.error == null) Icons.Default.CheckCircle else Icons.Default.Error,
when {
isFailed -> Icons.Default.Error
else -> Icons.Default.CheckCircle
},
contentDescription = null,
tint = if (result.verified && result.error == null)
MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error,
tint = when {
isFailed -> MaterialTheme.colorScheme.error
isVerifiedWithHash -> MaterialTheme.colorScheme.primary
else -> MaterialTheme.colorScheme.secondary // verified, no hash
},
modifier = Modifier.size(18.dp),
)
Column(modifier = Modifier.weight(1f)) {
@@ -241,19 +252,21 @@ private fun CopyResultRow(result: CopyResult) {
"${result.photo.filename}${result.destinationPath.fileName}"
else result.photo.filename
Text(displayName, style = MaterialTheme.typography.bodySmall, fontWeight = FontWeight.Medium)
if (result.error != null) {
Text(result.error, style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.error)
} else {
Text(
"${result.photo.sizeBytes / 1024} KB • SHA-256: ${result.destSha256.take(12)}",
when {
isFailed -> result.error ?: "Failed"
isVerifiedWithHash -> "SHA-256 verified · ${result.photo.sizeBytes / 1024} KB"
else -> "Copied · ${result.photo.sizeBytes / 1024} KB (no remote checksum)"
},
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
color = when {
isFailed -> MaterialTheme.colorScheme.error
else -> MaterialTheme.colorScheme.onSurfaceVariant
},
)
}
}
}
}
@Composable
private fun DeleteConfirmCard(count: Int, onConfirm: () -> Unit, onCancel: () -> Unit) {