aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-22 10:45:15 +0200
committerDanilo M. <danix@danix.xyz>2026-10-22 10:45:15 +0200
commit711af15b9cc3327a5b276ea5dc9faa5ad1b5a987 (patch)
treef0a63cf74944298a4bab177c5f203753f475dc89
parent295274d3899ed08bb51b57a800452e4c5f1842b5 (diff)
downloadmygram-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>
-rw-r--r--AGENTS.md24
-rw-r--r--docs/superpowers/specs/2026-10-22-mygram-droid-design.md167
2 files changed, 180 insertions, 11 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 2e9f7a8..d190de9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,11 +11,12 @@ photos and videos in a remote-friendly, D-pad driven TV interface.
## Scope
-- Read-only browsing: list folders, open a folder feed, view media
- fullscreen, step through items with the remote.
-- Server address entered once on first launch and stored on the device.
-- Out of scope unless asked: uploading, editing, deleting, accounts beyond
- what the instance itself requires, phone/tablet layouts.
+- Browsing: folder rows, folder grids, reels, fullscreen viewer driven by
+ the remote. The only write is toggling a like.
+- Instance found by LAN scan on first launch (manual URL as fallback),
+ logged in with the viewer password, both stored on the device.
+- Out of scope unless asked: uploading, editing, deleting, admin features,
+ phone/tablet layouts.
## Target device
@@ -41,13 +42,14 @@ photos and videos in a remote-friendly, D-pad driven TV interface.
- The real instance address is personal, keep it out of the repo. It is in
agent memory, or ask the user.
-## Open decisions
+## Design
-Not yet settled, ask before assuming:
-- Language and UI toolkit (e.g. Kotlin with Compose for TV vs Leanback).
-- Minimum Android TV API level.
-- Which Foldergram API endpoints to use; read the instance's actual API,
- do not guess routes.
+The approved design is
+`docs/superpowers/specs/2026-10-22-mygram-droid-design.md`: Java, minSdk
+34, Glide and Media3 ExoPlayer as the only dependencies, viewer-password
+login, subnet-scan instance discovery. Follow it; change it only with the
+user. For Foldergram endpoints read upstream `docs/api.md` and probe the
+live instance, do not guess routes or fields.
## Conventions
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.