# Gallery Upload — Android client Native uploader for the photogallery `POST /upload` endpoint. Pick or share a photo, add a caption and date, post. Credentials are entered once and sealed with a key held in the Android Keystore. The server is not modified by this app. Everything it needs, it does client side. ## Build ```bash cd android nix develop # Android SDK 36, build-tools 36.0.0, JDK 17, gradle gradle wrapper # first time only, generates ./gradlew ./gradlew assembleDebug adb install -r app/build/outputs/apk/debug/app-debug.apk ``` The dev shell exports `ANDROID_HOME`, `JAVA_HOME` and a `GRADLE_OPTS` that points AGP at the nix-provided `aapt2`. Without that last one AGP downloads a generic-linux `aapt2` from Maven which will not run on NixOS. ## Release signing `app/build.gradle.kts` wires up a release signing config only when `android/keystore.properties` exists. Both that file and `*.jks` are gitignored — keep the keystore outside the repo and back it up somewhere you will still have in five years, because losing it means you can never upgrade an installed build in place. ```bash mkdir -p ~/keys keytool -genkeypair -v \ -keystore ~/keys/photogallery-release.jks \ -alias photogallery \ -keyalg RSA -keysize 4096 -validity 10000 ``` Then write `android/keystore.properties`: ```properties storeFile=/home/you/keys/photogallery-release.jks storePassword=… keyAlias=photogallery keyPassword=… ``` ```bash ./gradlew assembleRelease ``` Without `keystore.properties` the release build still runs, it just comes out unsigned. ## How it maps onto the server | Server behaviour | What the app does about it | |---|---| | Slug derived from the uploaded filename | Sends `yyyy-MM-dd-HHmmss.jpg`, generated once at enqueue | | Same filename overwrites the same post | Reused across retries, which makes retries idempotent | | Uploaded file published verbatim as "full size" | Strips EXIF, including GPS, before upload | | Extension check rejects HEIC | Converts HEIC to JPEG; JPEG and PNG pass through losslessly | | nginx `client_max_body_size 20M` | Steps quality, then resolution, if the result exceeds 18 MiB | | Thumbnailer ignores EXIF orientation | Bakes rotation into the pixels rather than relying on the tag | | nginx only proxies `/upload`, so `/health` 404s | Tests credentials with `GET /upload` instead | | Replies `uploaded .jpg` in plain text | Parses that into `/photo//` for the "Open" link |