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.
71 lines
2.7 KiB
Markdown
71 lines
2.7 KiB
Markdown
# 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
|
|
|
|
```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 <slug>.jpg` in plain text | Parses that into `<base>/photo/<slug>/` for the "Open" link |
|