droid may do

3691fcb · Gabriel Morell · 2026-08-29 21:53

54 files +2272 -0

Files changed

added android/.gitignore
+12 −0
@@ -0,0 +1,12 @@
1 +*.iml
2 +.gradle/
3 +local.properties
4 +.idea/
5 +.DS_Store
6 +build/
7 +captures/
8 +.externalNativeBuild
9 +.cxx/
10 +*.apk
11 +*.aab
12 +keys/release.jks
added android/README.md
+96 −0
@@ -0,0 +1,96 @@
1 +# LambentEx Android client
2 +
3 +Native Android client for [LambentEx](../). Everything it does goes over the
4 +Phoenix channels at `/socket` — there is no REST API and nothing to log into.
5 +See [../docs/socket.md](../docs/socket.md) for the protocol.
6 +
7 +Sister app to `../../waxx/android` in shape, not in dress: same Kotlin/Compose
8 +shell, but the palette and font come from this app's own web UI rather than the
9 +shared Casabeza set.
10 +
11 +## Stack
12 +
13 +| Layer | Choice |
14 +|---|---|
15 +| Language / UI | Kotlin 2.0 · Jetpack Compose · Material 3 |
16 +| Transport | `com.github.dsrees:JavaPhoenixClient` (0.3.4, via jitpack) |
17 +| Storage | SharedPreferences — a LAN address, no credential |
18 +| Scanner | `com.journeyapps:zxing-android-embedded` (`ScanContract`) |
19 +| Build | Gradle 9.0 · AGP 8.5.2 · JDK 17 |
20 +| minSdk | 26 (Android 8) · targetSdk 34 |
21 +
22 +Single activity, single module, no DI, no ViewModels — `LambentSocket` is an
23 +object holding one socket and a `StateFlow` per channel, and the screens
24 +collect it. The server publishes whole state on a timer, so every screen is a
25 +reducer over pushes exactly like its LiveView is.
26 +
27 +## Layout
28 +
29 +```
30 +android/
31 + app/src/main/
32 + java/org/neiam/lambent/app/
33 + MainActivity.kt # connect gate, tabs, snackbar for notices
34 + data/
35 + LambentSocket.kt # the socket, four channels, all commands
36 + Models.kt # Device / Machine / Link / Service + parsing
37 + BaseUrlStore.kt # where the box lives
38 + ui/
39 + ConnectScreen.kt # QR scan or type an address
40 + DevicesScreen.kt # rename / place / order / poke / find-by-IP
41 + MachinesScreen.kt # speed, brightness, persist, quit, previews, library
42 + LinksScreen.kt # toggle / persist / quit, create with channel
43 + ServicesScreen.kt # list + delete, and forget-this-box
44 + theme/ # the afterdark palette and TF2 Build
45 + res/font/tf2build.ttf # converted from assets/css/fonts/TF2Build.woff
46 +```
47 +
48 +## Theme
49 +
50 +`ui/theme/Theme.kt` is the web UI's daisyUI `afterdark` theme from
51 +`assets/tailwind.config.js`, mapped onto a Material 3 `darkColorScheme` —
52 +primary `#7B79B5`, accent `#fef3c7`, base `#201D65` on a `#38357F` background.
53 +There is only the one scheme, because the web app is dark-only; honouring the
54 +system light mode would mean inventing a palette that doesn't exist.
55 +
56 +`ui/theme/Typography.kt` uses TF2 Build, the same face the web UI loads for
57 +`font-build`. It's a display font there — titles and nav only — so it stays on
58 +the display/headline/title slots and the platform sans carries body copy. The
59 +TTF was converted from the shipped `.woff` with fontTools; to regenerate:
60 +
61 +```sh
62 +python3 -c "from fontTools.ttLib import TTFont; f=TTFont('assets/css/fonts/TF2Build.woff'); f.flavor=None; f.save('android/app/src/main/res/font/tf2build.ttf')"
63 +```
64 +
65 +## Building
66 +
67 +Needs an Android SDK and **JDK 17** — AGP 8.5.2 does not run on JDK 22+, so
68 +point `JAVA_HOME` at 17 (or use Android Studio's bundled JDK) even if a newer
69 +one is your default.
70 +
71 +```sh
72 +cd android
73 +./gradlew :app:assembleDebug
74 +./gradlew :app:installDebug # or: adb install -r app/build/outputs/apk/debug/app-debug.apk
75 +```
76 +
77 +## Connecting
78 +
79 +Open the ⬛ QR icon in the web UI (top right of any page) and scan it, or type
80 +the address by hand — a bare `172.16.13.5` is fine, port 8033 is assumed and
81 +`http://` is not coerced to https, because this is a LAN box with no
82 +certificate. The address is remembered; **Services → Forget this box** clears
83 +it.
84 +
85 +Cleartext HTTP is enabled in the manifest for the same reason.
86 +
87 +## Not shipped
88 +
89 +- **Creating custom machines.** The library sheet starts a machine from a
90 + library entry; the full machine form is a polymorphic embed per machine type
91 + and would be a screen of its own. The `create` command on the channel already
92 + accepts the same params the web form posts.
93 +- **Bulk links.** `LambentSocket.createLinks` speaks the `create_bulk` command,
94 + but no screen calls it yet.
95 +- **Creating services.** Not offered by the web UI either — those three form
96 + components are still stubs.
added android/app/build.gradle.kts
+61 −0
@@ -0,0 +1,61 @@
1 +plugins {
2 + alias(libs.plugins.android.application)
3 + alias(libs.plugins.kotlin.android)
4 + alias(libs.plugins.kotlin.compose)
5 +}
6 +
7 +android {
8 + namespace = "org.neiam.lambent.app"
9 + compileSdk = 34
10 +
11 + defaultConfig {
12 + applicationId = "org.neiam.lambent.app"
13 + minSdk = 26
14 + targetSdk = 34
15 + versionCode = 1
16 + versionName = "0.1.0"
17 + }
18 +
19 + buildTypes {
20 + release {
21 + isMinifyEnabled = false
22 + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
23 + }
24 + }
25 +
26 + compileOptions {
27 + sourceCompatibility = JavaVersion.VERSION_17
28 + targetCompatibility = JavaVersion.VERSION_17
29 + }
30 + kotlinOptions {
31 + jvmTarget = "17"
32 + }
33 + buildFeatures {
34 + compose = true
35 + }
36 + packaging {
37 + resources {
38 + excludes += "/META-INF/{AL2.0,LGPL2.1}"
39 + }
40 + }
41 +}
42 +
43 +dependencies {
44 + implementation(libs.androidx.core.ktx)
45 + implementation(libs.androidx.lifecycle.runtime)
46 + implementation(libs.androidx.lifecycle.runtime.compose)
47 + implementation(libs.androidx.activity.compose)
48 +
49 + implementation(platform(libs.compose.bom))
50 + implementation(libs.compose.ui)
51 + implementation(libs.compose.ui.tooling.preview)
52 + implementation(libs.compose.material3)
53 + implementation(libs.compose.material.icons)
54 + debugImplementation(libs.compose.ui.tooling)
55 +
56 + implementation(libs.kotlinx.coroutines)
57 +
58 + // the whole api is channels; there is no rest client to speak of
59 + implementation(libs.phoenix.client)
60 + implementation(libs.zxing.android.embedded)
61 +}
added android/app/proguard-rules.pro
+10 −0
@@ -0,0 +1,10 @@
1 +# Keep Retrofit / kotlinx.serialization metadata.
2 +-keepattributes *Annotation*, InnerClasses
3 +-dontnote kotlinx.serialization.AnnotationsKt
4 +-keep,includedescriptorclasses class org.neiam.waxx.app.**$$serializer { *; }
5 +-keepclassmembers class org.neiam.waxx.app.** {
6 + *** Companion;
7 +}
8 +-keepclasseswithmembers class org.neiam.waxx.app.** {
9 + kotlinx.serialization.KSerializer serializer(...);
10 +}
added android/app/src/main/AndroidManifest.xml
+30 −0
@@ -0,0 +1,30 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3 +
4 + <uses-permission android:name="android.permission.INTERNET" />
5 + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
6 + <uses-permission android:name="android.permission.CAMERA" />
7 + <uses-feature android:name="android.hardware.camera" android:required="false" />
8 +
9 + <application
10 + android:allowBackup="true"
11 + android:dataExtractionRules="@xml/data_extraction_rules"
12 + android:icon="@mipmap/ic_launcher"
13 + android:label="@string/app_name"
14 + android:roundIcon="@mipmap/ic_launcher"
15 + android:supportsRtl="true"
16 + android:theme="@style/Theme.LambentEx"
17 + android:usesCleartextTraffic="true">
18 +
19 + <activity
20 + android:name=".MainActivity"
21 + android:exported="true"
22 + android:label="@string/app_name"
23 + android:theme="@style/Theme.LambentEx">
24 + <intent-filter>
25 + <action android:name="android.intent.action.MAIN" />
26 + <category android:name="android.intent.category.LAUNCHER" />
27 + </intent-filter>
28 + </activity>
29 + </application>
30 +</manifest>
added android/app/src/main/java/org/neiam/lambent/app/MainActivity.kt
+158 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/data/BaseUrlStore.kt
+36 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/data/LambentSocket.kt
+248 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/data/Models.kt
+127 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/ui/ConnectScreen.kt
+99 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/ui/DevicesScreen.kt
+190 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/ui/LinksScreen.kt
+234 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/ui/MachinesScreen.kt
+194 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/ui/ServicesScreen.kt
+82 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/ui/theme/Theme.kt
+77 −0

Click to load diff…

added android/app/src/main/java/org/neiam/lambent/app/ui/theme/Typography.kt
+28 −0

Click to load diff…

added android/app/src/main/res/drawable/ic_launcher_foreground.xml
+16 −0

Click to load diff…

modified ?
+0 −0

Click to load diff…

added android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+6 −0

Click to load diff…

added android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+6 −0

Click to load diff…

added android/app/src/main/res/values/colors.xml
+4 −0

Click to load diff…

added android/app/src/main/res/values/strings.xml
+3 −0

Click to load diff…

added android/app/src/main/res/values/themes.xml
+9 −0

Click to load diff…

added android/app/src/main/res/xml/data_extraction_rules.xml
+6 −0

Click to load diff…

added android/build.gradle.kts
+6 −0

Click to load diff…

added android/gradle.properties
+4 −0

Click to load diff…

added android/gradle/libs.versions.toml
+34 −0

Click to load diff…

modified ?
+0 −0

Click to load diff…

added android/gradle/wrapper/gradle-wrapper.properties
+7 −0

Click to load diff…

added android/gradlew
+251 −0

Click to load diff…

added android/gradlew.bat
+94 −0

Click to load diff…

added android/settings.gradle.kts
+25 −0

Click to load diff…

added docs/socket.md
+119 −0

Click to load diff…

Parents: 18ced42