- add `help` target and make it the default goal (bare `make` lists targets) - add `install-android-app`: build debug APK and install on the selected device - device selection via SERIAL/$ANDROID_SERIAL; several devices list and stop - document the target in android/README.md
4.4 KiB
galo Android client
Native Android client for a self-hosted galo instance: browse albums and media, watch photos/videos/360 panoramas, follow live album updates, and upload from the device with a persistent background queue and offline cache.
Kotlin + Jetpack Compose, minSdk 28, targetSdk 35. The module is standalone:
it does not touch backend/ or frontend/, and consumes the existing HTTP API
without server changes.
Requirements
- JDK 17
- Android SDK (
platforms;android-35,build-tools;35.0.0,platform-tools) ANDROID_HOMEpointing at the SDK, orandroid/local.propertieswithsdk.dir=<path>
Build
cd android
./gradlew :app:assembleDebug # debug APK
./gradlew test # JVM unit + Robolectric UI tests
./gradlew lintDebug # Android lint
./gradlew :app:assembleRelease # unsigned release APK without a keystore
From the repository root:
make android # runs :app:assembleDebug (ANDROID_SDK=... to override)
make install-android-app # build and install the debug APK on a device
install-android-app builds the debug APK and installs it onto the sole connected
device. To choose a device explicitly, pass its serial number:
make install-android-app SERIAL=emulator-5554
SERIAL also falls back to $ANDROID_SERIAL. When several devices are connected and
SERIAL is not set, the target prints the list of devices (only those in the device
state) and stops without installing. ADB=... overrides the adb binary (defaults to
$(ANDROID_SDK)/platform-tools/adb).
Debug output: android/app/build/outputs/apk/debug/app-debug.apk.
Signing (release)
The release keystore and credentials live outside the repository. Point the
build at a properties file with GALO_KEYSTORE_PROPERTIES=/path/keystore.properties,
or place an ignored android/keystore.properties:
storeFile=/secure/galo-release.jks
storePassword=...
keyAlias=galo
keyPassword=...
Without it, assembleRelease still resolves and produces an unsigned APK; no
secrets are stored in the repository.
First run
- Enter the instance base URL (e.g.
https://photos.example.com) and confirm — the app checksGET /api/health. - Plain
http://is blocked by default. For a trusted LAN instance, enable Небезопасное соединение (только LAN); the UI then marks the connection as insecure. The manifest permits cleartext, but the client itself refuses HTTP until you opt in. - Log in or register. Tokens are stored in the platform Keystore
(EncryptedSharedPreferences) and refreshed transparently on
401.
Upload notes
- Uses the system Photo Picker; pick files, then choose the destination album.
- One file per multipart POST (the server's batch upload is fail-fast and caps the total body size), so each file gets its own status and retry.
- Queue is persisted in Room and resumes after an app/device restart. Large and video uploads show a foreground progress notification; transient failures retry with exponential backoff (up to 3 attempts).
- Optional Только Wi-Fi limits uploads to unmetered networks; jobs wait in the queue until Wi-Fi is available.
- HEIC/HEIF originals are converted to JPEG on-device (the server decodes only JPEG/PNG/GIF/BMP/TIFF). Capture date and geolocation are carried over via EXIF and the base filename is preserved. Note: for HEIC sources the stored "original" is the converted JPEG.
Offline cache
Album metadata and photo pages are cached in Room; media is cached by Coil keyed by the object path without the signed query, so the cache is reused when the signature rotates. Albums and photo grids open from cache when offline.
Live updates
While an album screen is active the client holds
GET /api/sse/albums/:id?token= and backfills missed changes via
GET /api/albums/:id/photos?since= after a reconnect.
Testing
cd android && ./gradlew test
Unit tests cover URL resolution, error mapping, token refresh, the offline cache/repository fallback, page merging, the object cache key, SSE event parsing/reconnect, HEIC detection, and the panorama camera math. Robolectric Compose tests cover the albums grid/offline banner, the viewer swipe navigation, and upload statuses.
Device-only checks (real server E2E, Wi-Fi-only gating, background continuation, HEIC conversion of a real asset) require a device or emulator.