neiam
/lambent_ex
Lambent Aether 5
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions success · Badges · Packages
Log in to mark this repository.
droid may do
3691fcb · Gabriel Morell · 2026-08-29 21:53
Files changed
added
android/.gitignore
+12
−0
| 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
| 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
| 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
| 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
| 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