A new Manage screen (reached from the Post screen) lists published posts with thumbnails and lets you edit a post's caption, date and tags or delete it to the server's trash, over the same /api/photos, /api/edit and /api/delete endpoints the web /manage uses. UploadClient gains listPhotos/editPost/deletePost/fetchThumbnail, parsing JSON with the built-in org.json (no new dependency) and posting edits/deletes as urlencoded forms. The app sends no Origin or Sec-Fetch-Site header, so it passes the server's same-origin CSRF check as a non-browser client; Basic auth is already attached on every request. UploadViewModel holds the manage state, loads the list, fetches thumbnails in the background, and applies edits/deletes optimistically.
Gallery Upload — Android client
Native uploader and manager for the photogallery server. Pick or share one or
many photos, add caption/date/tags, and post. A Manage screen lists the
published posts and edits their caption/date/tags or deletes them (to the
server's trash) over the same /api/* endpoints the web /manage uses.
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; the management calls send no browser headers, so they pass the server's same-origin (CSRF) check as a non-browser client.
Build
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.
mkdir -p ~/keys
keytool -genkeypair -v \
-keystore ~/keys/photogallery-release.jks \
-alias photogallery \
-keyalg RSA -keysize 4096 -validity 10000
Then write android/keystore.properties:
storeFile=/home/you/keys/photogallery-release.jks
storePassword=…
keyAlias=photogallery
keyPassword=…
./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 <slug>.jpg in plain text |
Parses that into <base>/photo/<slug>/ for the "Open" link |