- 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
117 lines
4.4 KiB
Markdown
117 lines
4.4 KiB
Markdown
# galo Android client
|
|
|
|
Native Android client for a self-hosted [galo](../README.md) 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_HOME` pointing at the SDK, or `android/local.properties` with
|
|
`sdk.dir=<path>`
|
|
|
|
## Build
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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`:
|
|
|
|
```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
|
|
|
|
1. Enter the instance base URL (e.g. `https://photos.example.com`) and confirm —
|
|
the app checks `GET /api/health`.
|
|
2. 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.
|
|
3. 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
|
|
|
|
```sh
|
|
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.
|