Initial public release.

This commit is contained in:
Joe Kearney 2026-03-01 17:03:10 -06:00
parent ed31acd60f
commit 58d87b11b7
249 changed files with 15831 additions and 4 deletions

104
subapp-sample/README.md Normal file
View file

@ -0,0 +1,104 @@
# KTag Sample Subapp
A minimal Jetpack Compose subapp template for creating new KTag applications.
## Overview
The Sample subapp provides a starting point for creating new subapps in the KTag ecosystem. It demonstrates the minimal required structure and can be copied as a template for new functionality.
## Architecture
The simplest possible subapp structure:
```
┌─────────────────────────────────────────────────────────┐
│ SampleActivity │
│ (Compose Host) │
│ • Single "Hello World" screen │
└─────────────────────────────────────────────────────────┘
```
## File Structure
```
src/main/java/club/clubk/ktag/apps/sample/
├── SampleActivity.kt # Main activity with Compose UI
├── SampleSubApp.kt # Subapp registration
└── SampleInitializer.kt # Startup initializer
```
## Creating a New Subapp
To create a new subapp based on this template:
1. **Copy the module**: Duplicate `subapp-sample` directory
2. **Rename the package**: Update package name in all files
3. **Update build.gradle.kts**: Change namespace
4. **Register the subapp**: Update `SampleSubApp` with new ID, name, icon
5. **Add to settings.gradle.kts**: Include new module
6. **Add dependency**: Include in main app's dependencies
## Required Components
### SubApp Interface
Every subapp must implement the `SubApp` interface:
```kotlin
class MySubApp : SubApp {
override val id = "myapp" // Unique identifier
override val name = "My App" // Display name
override val icon = R.drawable.ic_myapp // Launcher icon
override fun createIntent(context: Context): Intent {
return Intent(context, MyActivity::class.java)
}
}
```
### Initializer
Register the subapp at startup:
```kotlin
class MyInitializer : Initializer<Unit> {
override fun create(context: Context) {
SubAppRegistry.register(MySubApp())
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}
```
### AndroidManifest.xml
Declare the activity and initializer:
```xml
<activity android:name=".MyActivity" android:exported="false" />
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup">
<meta-data
android:name=".MyInitializer"
android:value="androidx.startup" />
</provider>
```
## Optional Features
For more complex subapps, consider adding:
| Feature | Reference |
|---------|-----------|
| Settings | See `subapp-koth` or `subapp-medic` |
| MQTT | Implement `SettingsSubApp` interface |
| BLE | See `subapp-bletool` or `subapp-koth` |
| USB Serial | See `subapp-terminal` |
| ViewModel | See `subapp-koth` or `subapp-medic` |
## Dependencies
- Jetpack Compose (Material3)
- AndroidX Startup (for initialization)

View file

@ -0,0 +1,41 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "club.clubk.ktag.apps.sample"
compileSdk = 36
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(project(":core"))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.startup)
}
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".SampleActivity"
android:exported="false"
android:label="Sample SubApp" />
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false">
<meta-data
android:name="club.clubk.ktag.apps.sample.SampleInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>

View file

@ -0,0 +1,30 @@
package club.clubk.ktag.apps.sample
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
class SampleActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(modifier = Modifier.fillMaxSize()) {
Box(contentAlignment = Alignment.Center) {
Text(
text = "Hello from Sample SubApp!",
style = MaterialTheme.typography.headlineMedium
)
}
}
}
}
}
}

View file

@ -0,0 +1,13 @@
package club.clubk.ktag.apps.sample
import android.content.Context
import androidx.startup.Initializer
import club.clubk.ktag.apps.core.SubAppRegistry
class SampleInitializer : Initializer<Unit> {
override fun create(context: Context) {
SubAppRegistry.register(SampleSubApp())
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}

View file

@ -0,0 +1,15 @@
package club.clubk.ktag.apps.sample
import android.content.Context
import android.content.Intent
import club.clubk.ktag.apps.core.SubApp
class SampleSubApp : SubApp {
override val id = "sample"
override val name = "Sample"
override val icon = R.drawable.ic_sample
override fun createIntent(context: Context): Intent {
return Intent(context, SampleActivity::class.java)
}
}

View file

@ -0,0 +1,43 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="83.962"
android:viewportHeight="84.052"
android:tint="#333333"
android:alpha="0.6">
<group android:scaleX="0.83244103"
android:scaleY="0.8333333"
android:translateX="7.034293"
android:translateY="7.0043335">
<path
android:pathData="m14.681,64.127q-0.305,-0.198 -0.611,-0.431 -0.287,-0.251 -0.611,-0.431 -1.796,-1.042 -3.538,-0.7 -1.742,0.341 -3.251,1.67l-0.269,0.233l2.299,2.299q0.539,-0.431 0.934,-0.575 0.377,-0.162 0.736,-0.09 0.377,0.054 0.772,0.341 0.413,0.269 0.952,0.7zM20.249,69.299q-1.042,-1.042 -2.065,-1.419 -1.006,-0.395 -1.976,-0.323 -0.988,0.054 -1.904,0.467 -0.934,0.395 -1.796,0.934 -0.862,0.503 -1.634,1.06 -0.79,0.539 -1.491,0.88 -0.7,0.341 -1.293,0.395 -0.611,0.036 -1.096,-0.449 -0.377,-0.377 -0.485,-0.736 -0.126,-0.377 -0.036,-0.718 0.072,-0.359 0.305,-0.665 0.216,-0.323 0.521,-0.629l-2.263,-2.263l-0.108,0.108q-0.916,0.916 -1.491,1.922 -0.593,0.988 -0.718,2.012 -0.126,1.024 0.287,2.083 0.395,1.042 1.437,2.083 0.934,0.934 1.868,1.257 0.934,0.287 1.868,0.18 0.916,-0.126 1.832,-0.539 0.898,-0.431 1.76,-0.934 0.862,-0.539 1.67,-1.06 0.808,-0.521 1.527,-0.844 0.718,-0.323 1.347,-0.305 0.629,-0.018 1.149,0.503 0.377,0.377 0.485,0.772 0.126,0.377 0.054,0.772 -0.09,0.377 -0.305,0.736 -0.216,0.359 -0.503,0.682l2.353,2.353q0.233,-0.269 0.395,-0.467 0.18,-0.216 0.305,-0.413 0.162,-0.198 0.287,-0.431 0.144,-0.216 0.323,-0.539 0.431,-0.754 0.647,-1.616 0.198,-0.88 0.144,-1.724 -0.072,-0.862 -0.413,-1.67 -0.341,-0.808 -0.988,-1.455zM18.219,79.016 L15.831,76.663q-0.647,0.539 -1.096,0.772 -0.431,0.216 -0.844,0.162 -0.413,-0.054 -0.898,-0.323 -0.485,-0.269 -1.221,-0.718l-2.676,2.963q2.012,1.904 3.951,2.047 1.94,0.144 4.005,-1.527 0.323,-0.251 0.593,-0.521 0.287,-0.251 0.575,-0.503z"
android:strokeWidth="0.446"
android:fillColor="#323031"
android:strokeColor="#323031"/>
<path
android:pathData="m36.521,60.032 l-16.991,-8.154l-3.233,3.233l8.244,16.901l3.197,-3.197l-5.676,-10.381q-0.305,-0.593 -0.647,-1.114 -0.359,-0.539 -0.718,-1.078 1.401,1.114 2.981,1.904 1.598,0.772 3.161,1.616l-1.383,1.419 1.581,2.946l2.802,-2.802l3.52,1.868z"
android:strokeWidth="0.446"
android:fillColor="#323031"
android:strokeColor="#323031"/>
<path
android:pathData="m51.051,45.501l-12.572,-12.572l-2.622,2.622l12.572,12.572zM40.329,42.394l-5.658,-5.658l-0.216,0.216q1.778,3.035 3.502,6.053 1.724,2.981 3.7,5.891 -2.963,-1.922 -5.963,-3.628 -3.017,-1.724 -6.071,-3.484l-0.126,0.126l5.765,5.765q0.072,0.072 0.341,0.233 0.251,0.144 0.557,0.305 0.287,0.144 0.557,0.305 0.287,0.144 0.413,0.233l6.107,4.562l2.838,-2.838l-4.67,-6.214q-0.09,-0.126 -0.251,-0.395 -0.144,-0.287 -0.287,-0.575 -0.162,-0.305 -0.305,-0.557 -0.162,-0.269 -0.233,-0.341zM40.904,55.649l-12.572,-12.572l-2.64,2.64l12.572,12.572z"
android:strokeWidth="0.446"
android:fillColor="#323031"
android:strokeColor="#323031"/>
<path
android:pathData="m54.284,25.35q-1.078,-1.078 -2.047,-1.652 -0.988,-0.593 -1.904,-0.647 -0.934,-0.072 -1.868,0.395 -0.952,0.449 -1.958,1.455l-1.868,1.868l2.299,2.299l0.108,-0.108q0.575,-0.575 1.042,-1.042 0.485,-0.485 0.988,-0.665 0.503,-0.18 1.096,0.054 0.593,0.198 1.383,0.988 0.575,0.575 0.808,1.024 0.233,0.449 0.18,0.862 -0.036,0.395 -0.323,0.79 -0.305,0.377 -0.754,0.826l-0.898,0.898l2.281,2.281l1.419,-1.419q1.06,-1.06 1.652,-2.012 0.593,-0.988 0.629,-1.958 0.018,-0.988 -0.539,-2.012 -0.539,-1.042 -1.724,-2.227zM55.847,40.706l-12.572,-12.572l-2.82,2.82l12.572,12.572z"
android:strokeWidth="0.446"
android:fillColor="#323031"
android:strokeColor="#323031"/>
<path
android:pathData="m67.108,29.445l-12.59,-12.59l-2.856,2.856l12.59,12.59zM73.053,23.5l-2.353,-2.353l-4.616,4.616l2.353,2.353z"
android:strokeWidth="0.446"
android:fillColor="#323031"
android:strokeColor="#323031"/>
<path
android:pathData="m73.358,2.684l-2.335,-2.335l-4.975,4.975l2.335,2.335zM77.651,7.91l-2.263,-2.263l-4.508,4.508l2.263,2.263zM83.739,12.813l-2.335,-2.335l-5.101,5.101l2.335,2.335zM77.256,19.297l-12.59,-12.59l-2.802,2.802l12.59,12.59z"
android:strokeWidth="0.446"
android:fillColor="#323031"
android:strokeColor="#323031"/>
</group>
</vector>