Fix upload rollback and update app icon

Upload rollback fix: after uploading a file, do not store the remote
mtime/etag from the upload response PROPFIND. Nextcloud and other
WebDAV servers can change a file's Last-Modified or ETag after upload
(thumbnail generation, checksums, folder aggregation). Storing stale
metadata caused the next sync to see remoteChanged=true and download
the file back, reverting the upload. Leaving remoteAfterTransfer=null
forces the SKIP reconciliation pass to fill in remote state from the
directory listing, which is the same source all future syncs use.

Icon: update foreground to thick ribbons with 3D highlight stripes
(blue/green/red/orange, width 12 + highlight 5); update background
to dark space theme with star dots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 15:59:59 +00:00
parent 897b685c70
commit ae10ed0c82
5 changed files with 106 additions and 37 deletions
@@ -107,11 +107,10 @@ class SyncEngine @Inject constructor(
when (decision) {
SyncDecision.UPLOAD -> {
var uploadedRemoteFile: RemoteFile? = null
val bytes = runCatching {
ensureRemoteDirs(provider, pair.remotePath, rel)
accessor.openInputStream(rel)?.use { stream ->
uploadedRemoteFile = provider.uploadFile(stream, "${pair.remotePath}/$rel", local!!.sizeBytes) { }.getOrThrow()
provider.uploadFile(stream, "${pair.remotePath}/$rel", local!!.sizeBytes) { }.getOrThrow()
}
local!!.sizeBytes
}.getOrElse { e ->
@@ -120,8 +119,12 @@ class SyncEngine @Inject constructor(
return@withPermit FileOutcome(failed = 1)
}
logEvent(pair.id, SyncEventType.FILE_UPLOADED, rel, null, bytes)
// Don't store remote metadata from upload response — the server (Nextcloud etc.)
// may change mtime/etag during post-upload processing. Leaving remoteModifiedAt
// null forces the SKIP reconciliation on the next sync to fill it in from the
// directory listing, which is the same source all future syncs will use.
FileOutcome(uploaded = 1, bytesTransferred = bytes,
newState = buildState(pair.id, rel, local!!, remoteAfterTransfer = uploadedRemoteFile))
newState = buildState(pair.id, rel, local!!, remoteAfterTransfer = null))
}
SyncDecision.DOWNLOAD -> {
val bytes = runCatching {
@@ -5,8 +5,41 @@
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Pure black background -->
<path android:pathData="M0,0 H108 V108 H0 Z"
android:fillColor="#000000"/>
<!-- Dark space background -->
<path
android:pathData="M0,0 H108 V108 H0 Z"
android:fillColor="#050F05"/>
<!-- Subtle dark green center glow -->
<path
android:pathData="M54,54 A40,40 0 1,0 54.01,54 Z"
android:fillColor="#0D1F0D"
android:fillAlpha="0.9"/>
<!-- Stars -->
<path android:fillColor="#FFFFFF" android:fillAlpha="0.9"
android:pathData="M18,12 A1.2,1.2 0 1,0 18.01,12 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.7"
android:pathData="M88,18 A0.9,0.9 0 1,0 88.01,18 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.8"
android:pathData="M12,55 A1.0,1.0 0 1,0 12.01,55 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.6"
android:pathData="M95,40 A0.8,0.8 0 1,0 95.01,40 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.9"
android:pathData="M25,90 A1.1,1.1 0 1,0 25.01,90 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.7"
android:pathData="M82,88 A0.9,0.9 0 1,0 82.01,88 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.5"
android:pathData="M96,72 A0.8,0.8 0 1,0 96.01,72 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.8"
android:pathData="M8,80 A1.0,1.0 0 1,0 8.01,80 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.6"
android:pathData="M70,8 A0.9,0.9 0 1,0 70.01,8 Z"/>
<path android:fillColor="#FFFFFF" android:fillAlpha="0.7"
android:pathData="M40,100 A0.8,0.8 0 1,0 40.01,100 Z"/>
<path android:fillColor="#AAFFAA" android:fillAlpha="0.5"
android:pathData="M92,94 A1.0,1.0 0 1,0 92.01,94 Z"/>
<path android:fillColor="#AAAAFF" android:fillAlpha="0.4"
android:pathData="M5,25 A0.8,0.8 0 1,0 5.01,25 Z"/>
</vector>
@@ -6,77 +6,110 @@
android:viewportHeight="108">
<!--
Four thick arcs arranged as an interlocked pinwheel.
Each arc sweeps ~210 degrees, rounded caps, radius 18 from center (54,54).
Draw order creates natural over/under at the four crossing points:
blue under green, green under red, red under orange, orange under blue (re-draw blue tip).
Four thick ribbons in an interlocked pinwheel knot.
Each ribbon sweeps 210 degrees clockwise on a radius-18 circle centered at (54,54).
Each ribbon is drawn as: base (width 12) + highlight stripe (width 5).
Over/under order: Blue under Green, Green under Red, Red under Orange, Orange under Blue tip.
Arc endpoints computed at radius 18, sweep 210 deg clockwise:
start angle end angle start point end point
270 (top) 120 (54, 36) (45, 70)
0 (right) 210 (72, 54) (39, 45)
90 (bot) 300 (54, 72) (63, 38)
180 (left) 390=30 (36, 54) (69, 63)
Arc start/end points (radius 18 from center 54,54):
Blue: start 270deg (54,36) end 120deg (45,70)
Green: start 90deg (54,72) end 300deg (63,38)
Red: start 0deg (72,54) end 210deg (39,45)
Orange: start 180deg (36,54) end 30deg (69,63)
-->
<!-- Blue — starts at top, sweeps clockwise to lower-left -->
<!-- Blue ribbon base (goes under Green start and Orange end) -->
<path
android:strokeColor="#2979FF"
android:strokeWidth="8.5"
android:strokeColor="#1565C0"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 54,36 A 18,18 0 1,1 45,70"/>
<!-- Blue ribbon highlight stripe -->
<path
android:strokeColor="#90CAF9"
android:strokeWidth="5"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 54,36 A 18,18 0 1,1 45,70"/>
<!-- Green — starts at bottom, sweeps clockwise to upper-right -->
<!-- Green ribbon base (over Blue start, under Red end) -->
<path
android:strokeColor="#00C853"
android:strokeWidth="8.5"
android:strokeColor="#2E7D32"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 54,72 A 18,18 0 1,1 63,38"/>
<!-- Green ribbon highlight stripe -->
<path
android:strokeColor="#A5D6A7"
android:strokeWidth="5"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 54,72 A 18,18 0 1,1 63,38"/>
<!-- Red — starts at right, sweeps clockwise to lower-left -->
<!-- Red ribbon base (over Green start, under Orange end) -->
<path
android:strokeColor="#E53935"
android:strokeWidth="8.5"
android:strokeColor="#C62828"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 72,54 A 18,18 0 1,1 39,45"/>
<!-- Red ribbon highlight stripe -->
<path
android:strokeColor="#EF9A9A"
android:strokeWidth="5"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 72,54 A 18,18 0 1,1 39,45"/>
<!-- Orange — starts at left, sweeps clockwise to upper-right -->
<!-- Orange ribbon base (over Red start, under Blue tip) -->
<path
android:strokeColor="#FF6D00"
android:strokeWidth="8.5"
android:strokeColor="#E65100"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 36,54 A 18,18 0 1,1 69,63"/>
<!-- Orange ribbon highlight stripe -->
<path
android:strokeColor="#FFCC80"
android:strokeWidth="5"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 36,54 A 18,18 0 1,1 69,63"/>
<!-- Re-draw blue start cap on top so it goes OVER orange end -->
<!-- Redraw Blue start cap on top so it goes OVER Orange end -->
<path
android:strokeColor="#2979FF"
android:strokeWidth="8.5"
android:strokeColor="#1565C0"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 54,36 A 18,18 0 0,1 62,37.5"/>
<path
android:strokeColor="#90CAF9"
android:strokeWidth="5"
android:fillColor="#00000000"
android:strokeLineCap="round"
android:pathData="M 54,36 A 18,18 0 0,1 62,37.5"/>
<!-- White sync circle at center -->
<!-- Black circle behind center sync icon -->
<path
android:fillColor="#000000"
android:pathData="M 45,54 A 9,9 0 1,0 63,54 A 9,9 0 1,0 45,54 Z"/>
<!-- Sync ring -->
<!-- White sync ring -->
<path
android:strokeColor="#FFFFFF"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:pathData="M 46.5,54 A 7.5,7.5 0 1,0 61.5,54 A 7.5,7.5 0 1,0 46.5,54 Z"/>
<!-- Top arrow head (pointing up) -->
<!-- Up arrow (pointing up) -->
<path
android:fillColor="#FFFFFF"
android:pathData="M 54,46.5 L 57,50.5 L 51,50.5 Z"/>
<!-- Bottom arrow head (pointing down) -->
<!-- Down arrow (pointing down) -->
<path
android:fillColor="#FFFFFF"
android:pathData="M 54,61.5 L 51,57.5 L 57,57.5 Z"/>