diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-22 10:45:15 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-22 10:45:15 +0200 |
| commit | 711af15b9cc3327a5b276ea5dc9faa5ad1b5a987 (patch) | |
| tree | f0a63cf74944298a4bab177c5f203753f475dc89 /docs/superpowers/specs/2026-10-22-mygram-droid-design.md | |
| parent | 295274d3899ed08bb51b57a800452e4c5f1842b5 (diff) | |
| download | mygram-droid-711af15b9cc3327a5b276ea5dc9faa5ad1b5a987.tar.gz mygram-droid-711af15b9cc3327a5b276ea5dc9faa5ad1b5a987.zip | |
Add approved app design spec
Design for the Android TV client: instance discovery by subnet scan,
viewer-password login, Home rows with blurred rotating background,
Reels, Liked and Folders tabs, and a fullscreen viewer with blurred
backdrop, auto-advance, progress bar and double-OK likes. AGENTS.md
now points at the spec instead of listing open decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers/specs/2026-10-22-mygram-droid-design.md')
| -rw-r--r-- | docs/superpowers/specs/2026-10-22-mygram-droid-design.md | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-10-22-mygram-droid-design.md b/docs/superpowers/specs/2026-10-22-mygram-droid-design.md new file mode 100644 index 0000000..958f0a6 --- /dev/null +++ b/docs/superpowers/specs/2026-10-22-mygram-droid-design.md @@ -0,0 +1,167 @@ +# mygram-droid design + +Date: 2026-10-22 + +Native Android TV client for a self-hosted Foldergram instance. Lean-back +browsing of folders, photos and (mostly vertical) videos with the remote, +plus shared likes. + +## Target + +- Chromecast with Google TV HD: Google TV 14 (API 34), 32-bit ARM, + about 1.4 GB RAM. +- Foldergram on the LAN, plain HTTP, default port 4141. The app allows + cleartext traffic. +- The instance runs in viewer-password mode: every route, including + `/thumbnails/...` and `/previews/...`, needs a session. + +## Stack + +- Java, minSdk 34, one `app` module. +- Built in a throwaway Docker container (JDK 17, SDK 34), same pattern as + the `tv-debloat` launcher. +- Dependencies: Glide (images) and Media3 ExoPlayer (video). Everything + else from the platform: `HttpURLConnection`, `org.json`, + `SharedPreferences`, `RenderEffect`, `MediaMetadataRetriever`. + +## Screens + +### Instance picker (first launch, or "Change instance" in Settings) + +- Scans the TV's /24 subnet on port 4141: `GET /api/health` on every + address, about 32 in parallel, short timeout, about 3 to 5 s total. +- A host is accepted only if the reply is JSON with `ok: true` and a + `storage` object. +- Each result shows its reverse-DNS name when available, else the IP. +- Extra entries: "Enter URL manually", "Rescan". +- On selection: `GET /api/auth/status`. If `accessMode` is `password`, + prompt for the password and `POST /api/auth/login`. If `public`, no + password. Save URL and password, then open Home. +- Limits: misses instances on another port or behind a reverse proxy; + manual entry covers those. + +### Top menu + +`Home · Reels · Liked · Folders · Settings`, always visible except in the +viewer. Up from the content reaches it. + +### Home + +- Background: a random image from `GET /api/feed?mode=random`, blurred and + scaled up, crossfading every N seconds (Settings, default 15 s). +- One horizontal row per folder from `GET /api/folders`, ordered by + `latestImageMtimeMs` descending, rows loaded lazily on scroll. +- Each row: about 10 random items from that folder, then a **Continue** + tile. +- OK on an item: viewer at that item, continuing through the folder's + shuffled list. OK on Continue: folder grid. + +### Folders + +Grid of all folders as avatar tiles (`avatarUrl`). OK: folder grid. + +### Folder grid + +- Fetches every page of `GET /api/folders/:slug/images` (60 per request) + and shuffles locally once per open. The API has no random mode for + folders. +- OK: viewer at that item, continuing in grid order. + +### Liked + +- Grid from `GET /api/likes`, newest like first. +- OK: viewer in that order. +- Posts unliked in the viewer are gone from the grid on return. + +### Reels + +Opens the viewer directly on `GET /api/reels?mode=random&seed=<random>`, +videos only, endless, paged with the same seed. + +### Viewer (fullscreen) + +- Item centered at its own aspect ratio over a blurred, scaled-up backdrop + that fills the side bars. +- Photo backdrop: the photo's own preview, decoded small (about 200 px) and + blurred with `RenderEffect`. +- Video backdrop: the server thumbnail, blurred, shown at once. It is then + replaced by a frame from the preview mp4 at a random time between 20 % + and 80 % of `durationMs` (`MediaMetadataRetriever.getFrameAtTime`, off + the main thread, cancelled on post change). On failure the thumbnail + stays. First and last frames are avoided because they are often black. +- Remote: + - Up / Down: previous / next post. + - Left / Right: seek -10 s / +10 s on video; previous / next slide on a + carousel post. + - OK: pause / play, applied immediately. + - Double OK (second press within about 300 ms): undo that pause toggle + and toggle the like instead. A large heart flashes in the center. + - Back: exit. +- Auto-advance: video end, or photo timer (Settings, default 8 s), goes to + the next post. Pausing stops the timer. +- Thin progress bar along the bottom edge for photos (timer) and videos + (playback position). Frozen while paused. +- Small pause icon at the bottom center while paused. +- Small heart at the bottom right while the post is liked. +- Media: `previewUrl` (images up to 1500 px wide, video mp4). One ExoPlayer + instance, reused across posts. + +### Settings + +- Change instance (reopens the picker). +- Viewer password. +- Photo duration (default 8 s). +- Background interval (default 15 s). + +## Architecture + +| Unit | Job | +| --- | --- | +| `Api` | `HttpURLConnection` + `org.json`. Folders, folder images, feed, reels, likes, like toggle, health, auth status, login. Resolves relative media URLs against the server URL. Sends `x-foldergram-intent: 1` on mutations. | +| `Discovery` | Subnet scan for the instance picker. | +| `Post` | Data class from feed-item JSON: `id`, folder, `mediaType`, width/height, `durationMs`, `thumbnailUrl`, `previewUrl`, `postType`, `mediaItems` (carousel slides). | +| `Likes` | In-memory set of liked post IDs, loaded from `/api/likes` at start, updated on each toggle. Drives every heart indicator. | +| `MainActivity` | Top menu and content area; Home, Liked, Folders, Settings as Fragments. | +| `HomeFragment` | Vertical RecyclerView of horizontal rows, rotating background. | +| `FolderGridActivity` | One folder, all pages, shuffled grid. | +| `ViewerActivity` | Plays a list of posts from a start index, or pages an endless source (Reels). Backdrop, progress bar, pause icon, hearts, double-OK. | +| `Prefs` | `SharedPreferences`: server URL, password, photo duration, background interval. | + +- Lists reach the viewer through a static in-memory holder; intents cannot + carry hundreds of posts. +- Session: `CookieHandler.setDefault(new CookieManager())` at app start. + `Api`, Glide's default fetcher and ExoPlayer's `DefaultHttpDataSource` all + use `HttpURLConnection` and share the cookie. `MediaMetadataRetriever` + gets the `Cookie` header passed explicitly. +- Login runs on app start with the stored password; the cookie lives in + memory only. +- Memory: Glide decodes at view size, backdrops decode small, the viewer + uses previews not originals. + +## Errors + +- Server unreachable or login fails at start: full-screen message with + Retry and Change instance. Never a crash. +- `401` mid-session: one silent re-login and retry; on second failure open + Settings. +- Thumbnail load failure: plain dark tile. +- Viewer media failure: message over the blurred thumbnail, auto-advance + after the photo timer. +- Like toggle failure: revert the heart, show a toast. +- Page fetch failure in Reels: retry on the next Down press. + +## Testing + +- One JVM unit test covering: `Post` parsing (single image, video, + carousel), URL resolution, Foldergram health-reply detection, and the + double-OK logic as a plain class with an injected clock. +- On-device check for every UI change: `KEYCODE_WAKEUP`, drive with + `adb shell input keyevent`, inspect with `adb exec-out screencap -p`. +- Before the first build, probe the live instance with `curl` to confirm + field names (`postType`, `mediaItems`, `/api/likes` item shape) instead + of trusting the upstream docs. + +## Out of scope + +Search, stories, moments, places, collections, admin features, uploads and +deletes, phone or tablet layouts, mDNS discovery. |
