A pushed git tag doesn't create a Gitea release object, so the publish step
404'd trying to attach the APK. Now it creates the release if absent (with
contents:write permission), then uploads. v1.0.65 was published manually.
Sync engine / providers:
- LocalAccessor: replace createOutputStream with writeAtomically (temp
sibling + rename/commit) for both JavaFile and SAF backends, so an
interrupted download no longer truncates the destination file.
- SyncEngine: use writeAtomically for DOWNLOAD and propagate downloadFile
failures via getOrThrow (was silently swallowed -> false success + state).
- WebDavProvider (covers Nextcloud/ownCloud): PUT to hidden temp then MOVE
onto destination, so a failed upload can't leave a truncated remote file.
- SftpProvider: upload to temp then rename onto destination.
Build / CI:
- compileSdk 34 -> 35 (was below targetSdk 35).
- Release signing reads keystore from local.properties or env (CI), with a
debug-key fallback so builds still succeed without secrets.
- Disable R8/minify for release (never exercised by CI; keeps signed release
behaving like the debug builds in use today).
- CI: run unit tests on every push/PR, build assembleRelease (signed when
KEYSTORE_BASE64 present), publish APK only on v* tags.
Triggers on v* tags — sets up Java 17 + Android SDK, builds a debug APK
(installable without a keystore), renames it SyncFlow-v<version>.apk, and
uploads it to the matching Gitea release via the API using the built-in token.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>