OXYGENHUB

README and changelog for the app's foundation (closes #2) 3f3508bd5f16

How to sign in, what works today and what is planned, the layout of the
code, and the screenshots rendered from the previews.
Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key f4ec0f2e7863
When
· 2026-10-24 22:32 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
3f3508bd5f16aee7568582819e69ad40a442e03a2fac756de30da440320a69f5
Depends on
0a2858c0dad3 1416009b06af 144cbf7bf2b5 206d05a042e7 57c0824300db 5ae4af48ddd2 9481bfa7c5d0 d07edbd2c022
Depended on by
d1f635be9442 e2df27a81e5d 0381bd8af55e db00632a61d6
In bookmarks
main
In tags
v0.1.0 v0.1.1 v0.1.2 v0.1.3 v0.1.4 v0.1.5 v0.1.6 v0.1.7 v0.1.8 v0.1.9
Edits
15 atoms, 8 tree operations, 8 files

Browse the tree at this change

added docs/screenshots/dashboard-phone.png +0 −0

No line changes.

added docs/screenshots/inbox-phone-dark.png +0 −0

No line changes.

added docs/screenshots/repositories-foldable.png +0 −0

No line changes.

added docs/screenshots/repositories-tablet-dark.png +0 −0

No line changes.

added docs/screenshots/sign-in-code.png +0 −0

No line changes.

added docs/screenshots/sign-in.png +0 −0

No line changes.

modified CHANGELOG.md +1 −0
…
1 unchanged lines
2 2
3 3
## Unreleased
4 4
5 +
- **Sign in, the hub's API, navigation and foldable layouts (#2):** sign in with the hub's device flow, as `oh auth login` does (a code to approve on the hub's `/device` page, with the password and second factor there, never in the app), against oxygenhub.io or any hub; the token sealed with a Keystore key, kept out of backups, and revoked on the hub when signing out. A hand-written Kotlin client for the hub's API: the user, repositories, a repository's issues, the inbox and marking it read. A dashboard (greeting, recent repositories, unread count), repositories with their issues side by side from a medium width up (a foldable open, a tablet), and the inbox with a badge; a navigation bar on a phone and a rail when wider; light and dark, in dynamic colour or the hub's own palette; loading, empty and error states with pull to refresh. The placeholder login (username and password) and tabs are gone. Unit tests for the client (against fixtures from hub-api's types) and the view models; README screenshots rendered from Compose previews without an emulator.
5 6
- **The project set up properly (#1):** the application id and package are `io.oxygenhub.android` (permanent), minSdk 31 (Android 12), the Gradle and build caches no longer tracked, `rust-core/` part of the repository (it was a nested, empty Git repository, so it had never been committed), and the Rust core built for arm64-v8a and x86_64 and its Kotlin bindings generated by the build itself (`buildRustCore`, `generateRustBindings`) instead of being committed. The pipeline has no jobs until OxygenFlow has an Android image (oxygenflow#19).
modified README.md +74 −5
…
1 unchanged lines
2 2
3 3
[![flow](.flow/stamp)](.flow/run)
4 4
5 -
![Status: Planning](https://img.shields.io/badge/Status-Planning-blue)
5 +
![Status: Early development](https://img.shields.io/badge/Status-Early%20development-blue)
6 6
![Platform: Android](https://img.shields.io/badge/Platform-Android-green)
7 7
![CI: Flow](https://img.shields.io/badge/CI-Flow-blue)
8 8
…
3 unchanged lines
12 12
13 13
---
14 14
15 -
## ✨ Features
15 +
<p>
16 +
  <img src="docs/screenshots/dashboard-phone.png" alt="The dashboard on a phone" width="200">
17 +
  <img src="docs/screenshots/sign-in-code.png" alt="Signing in: the code to approve, dark" width="200">
18 +
  <img src="docs/screenshots/inbox-phone-dark.png" alt="The inbox, dark" width="200">
19 +
</p>
20 +
<p>
21 +
  <img src="docs/screenshots/repositories-foldable.png" alt="Repositories and a repository's issues side by side on an open foldable" width="400">
22 +
  <img src="docs/screenshots/repositories-tablet-dark.png" alt="Repositories on a tablet, dark" width="400">
23 +
</p>
24 +
25 +
## ✅ What works today
26 +
27 +
* 🔑 **Sign in with your browser** to oxygenhub.io or any hub (#2). The app
28 +
  shows a code; you approve it on the hub's `/device` page, signed in there
29 +
  with your password and second factor, so the app never sees either. The
30 +
  device token is sealed with a key in the Android Keystore (StrongBox where
31 +
  the phone has one), never written in the clear, logged or backed up.
32 +
  Signing out (Account) revokes it on the hub.
33 +
* 🏠 **Dashboard:** a greeting, your newest repositories, the unread count.
34 +
* 📚 **Repositories:** yours or all you can see, filtered as you type; a
35 +
  repository's **issues** (open, closed, all; pinned first). An issue opens on
36 +
  the hub's web page for now (#4 brings it into the app).
37 +
* 📥 **Inbox:** notifications, unread first, with a badge on the navigation;
38 +
  opening one marks it read, and the whole inbox can be marked read.
39 +
* 📱 **Phone, foldable and tablet:** a navigation bar on a phone, a rail when
40 +
  wider; repositories and their issues side by side from a medium width up,
41 +
  so a foldable opened (Pixel 10 Pro Fold) shows both, kept off the hinge.
42 +
* 🌗 **Light and dark**, with the wallpaper's colours (dynamic colour), or the
43 +
  hub's own palette.
44 +
* Pull to refresh everywhere, with loading, empty and error states.
45 +
46 +
The screenshots are rendered from Compose previews with made-up data, no
47 +
emulator: `scripts/screenshots.sh`.
48 +
49 +
## 🛣️ Planned
50 +
51 +
The plan is the repository's issues: browse code, history and drops (#3),
52 +
issues and proposals (#4), Flow runs and live logs (#5), trust and sealed
53 +
approvals (#6), releases (#7), and the phone as an OxygenFlow runner (#8–#11).
54 +
16 55
* 🧬 **Commutative History:** Native visualization of the change graph.
17 56
* 🔀 **Offline-First Conflicts:** Resolve conflicts on the go; they're just data.
18 57
* 🌊 **Job Flow Control:** Orchestrate OxygenFlow CI/CD pipelines right from your phone.
58 +
59 +
## 🔑 Signing in
19 60
61 +
1. Open the app. The hub is `https://oxygenhub.io` unless you change it (any
62 +
   HTTPS address of a hub works; the last one is remembered).
63 +
2. Tap **Sign in**. The app shows a code such as `BCDF-GHJK`.
64 +
3. Tap **Open the hub to approve** (or open `/device` on the hub from a
65 +
   computer and type the code), sign in there if you need to, check what the
66 +
   app asks for, and approve it. You may untick scopes.
67 +
4. The app signs in on its own within a few seconds.
68 +
69 +
The app asks for `repo:read` and `user:read`. The token lasts while it is
70 +
used and ends after 90 days without use; it is listed on the hub under
71 +
Settings, Tokens as "device: OxygenHub for Android on" and the phone's model.
72 +
20 73
## 🛠️ Setup
21 74
22 75
1. Clone the repository with `oxy clone`, and fetch the pinned Oxygen
…
2 unchanged lines
25 78
   point `local.properties` at it (`sdk.dir=…`) or set `ANDROID_HOME`.
26 79
3. Install the Rust side: `cargo install cargo-ndk` and
27 80
   `rustup target add aarch64-linux-android x86_64-linux-android`.
28 -
4. Build and test; the build compiles rust-core for arm64-v8a and x86_64
29 -
   and generates its Kotlin bindings itself:
81 +
4. Build, test and lint; the build compiles rust-core for arm64-v8a and
82 +
   x86_64 and generates its Kotlin bindings itself:
30 83
   ```bash
31 -
   ./gradlew :app:assembleDebug :app:testDebugUnitTest
84 +
   ./gradlew :app:assembleDebug :app:testDebugUnitTest :app:lintDebug
32 85
   ```
86 +
87 +
## 🧭 Layout of the code
88 +
89 +
Under `app/src/main/java/io/oxygenhub/android`:
90 +
91 +
* `api/`: the hub's JSON types (by hand, from oxygenhub's `hub-api`), the
92 +
  `HubApi` client over OkHttp, and the device sign-in (`DeviceAuth`).
93 +
* `auth/`: the session, `KeystoreSessionStore` (the sealed token) and
94 +
  `SessionManager` (sign in, sign out, a refused token).
95 +
* `ui/`: one package per screen, each a view model and its composables;
96 +
  `ui/nav` holds the routes and the adaptive navigation; `ui/common` the
97 +
  loading, empty and error states.
98 +
99 +
The unit tests (`app/src/test`) run the client against MockWebServer with
100 +
JSON fixtures written from hub-api's types, and the view models against a
101 +
fake hub.
33 102
34 103
## 🏗️ CI/CD
35 104
…
2 unchanged lines