Compare commits

...

10 commits

16 changed files with 136 additions and 156 deletions

View file

@ -9,20 +9,20 @@ val keystoreProperties = Properties ()
keystoreProperties . load ( FileInputStream ( rootProject . file ("keystore.properties") ) )
plugins {
id ("com.android.application") . version ("8.7.2")
id ("org.jetbrains.kotlin.android") . version ("2.0.0")
id ("org.jetbrains.kotlin.plugin.compose") . version ("2.0.0")
id ("org.jetbrains.kotlin.plugin.serialization") . version ("2.0.0")
id ("com.android.application") . version ("8.8.0")
id ("org.jetbrains.kotlin.android") . version ("2.1.10")
id ("org.jetbrains.kotlin.plugin.compose") . version ("2.1.10")
id ("org.jetbrains.kotlin.plugin.serialization") . version ("2.1.10")
}
android {
namespace = "com.kernelmaft.zanbur"
compileSdk = 34
compileSdk = 35
defaultConfig {
applicationId = "com.kernelmaft.zanbur"
minSdk = 31
targetSdk = 34
minSdk = 35
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
@ -47,10 +47,11 @@ android {
}
}
compileOptions {
targetCompatibility = JavaVersion . VERSION_11
// Required even though we don't have any Java sources because it needs to match Kotlin's JVM version
targetCompatibility = JavaVersion . VERSION_23
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "23"
}
buildFeatures {
compose = true
@ -60,19 +61,18 @@ android {
dependencies {
// Android runtime libraries
implementation ( "com.google.android.material" , "material" , "1.12.0" )
implementation ( "androidx.activity" , "activity-compose" , "1.9.3" )
implementation ( "androidx.core" , "core-ktx" , "1.13.1" )
implementation ( "androidx.activity" , "activity-compose" , "1.10.0" )
implementation ( "androidx.core" , "core-ktx" , "1.15.0" )
implementation ( "androidx.compose.material3" , "material3" , "1.3.1" )
implementation ( "androidx.compose.ui" , "ui" , "1.7.5" )
implementation ( "androidx.compose.ui" , "ui-graphics" , "1.7.5" )
debugImplementation ( "androidx.compose.ui" , "ui-tooling" , "1.7.5" )
implementation ( "androidx.datastore" , "datastore-preferences" , "1.1.1" )
implementation ( "androidx.compose.ui" , "ui" , "1.7.7" )
implementation ( "androidx.compose.ui" , "ui-graphics" , "1.7.7" )
debugImplementation ( "androidx.compose.ui" , "ui-tooling" , "1.7.7" )
implementation ( "androidx.lifecycle" , "lifecycle-runtime-ktx" , "2.8.7" )
implementation ( "org.jetbrains.kotlinx" , "kotlinx-coroutines-android" , "1.9.0" )
implementation ( "org.jetbrains.kotlinx" , "kotlinx-serialization-json" , "1.7.3" )
implementation ( "org.jetbrains.kotlinx" , "kotlinx-coroutines-android" , "1.10.1" )
implementation ( "org.jetbrains.kotlinx" , "kotlinx-serialization-json" , "1.8.0" )
// Other libraries
implementation ( "io.github.davidepianca98" , "kmqtt-common" , "0.4.8" )
implementation ( "io.github.davidepianca98" , "kmqtt-client" , "0.4.8" )
implementation ( "io.github.davidepianca98" , "kmqtt-common" , "1.0.0" )
implementation ( "io.github.davidepianca98" , "kmqtt-client" , "1.0.0" )
}
tasks . withType ( KotlinCompile :: class ) . all {

View file

@ -4,17 +4,14 @@
<uses-permission android:name = "android.permission.INTERNET" />
<application
android:label = "Zanbur"
android:icon = "@mipmap/ic_launcher"
android:label = "@string/app_name"
android:roundIcon = "@mipmap/ic_launcher_round"
android:supportsRtl = "true"
android:theme = "@style/Theme.Zanbur" >
android:supportsRtl = "true" >
<activity
android:name = ".MainActivity"
android:exported = "true"
android:label = "@string/title_activity_main"
android:theme = "@style/Theme.Zanbur" >
android:name = "com.kernelmaft.zanbur.ui.MainActivity"
android:exported = "true" >
<intent-filter>
<action android:name = "android.intent.action.MAIN" />

View file

@ -1,25 +0,0 @@
package com.kernelmaft.zanbur
object AppState {
val groups : List <Group> get () = groupsAsMutable
private var groupsAsMutable : List <Group> = emptyList ()
private val subscribers : MutableList < ( List <Group> ) -> Unit > = mutableListOf ()
fun subscribe ( subscriber : ( List <Group> ) -> Unit ) = subscribers . add (subscriber)
fun addGroup ( group : Group ) {
groupsAsMutable += group
subscribers . forEach { it (groups) }
}
fun setCurrentScene ( groupId : Int , scene : Scene ) {
groupsAsMutable = groupsAsMutable . mapIndexed { index , group ->
if ( index == groupId ) group . copy ( currentScene = scene )
else group
}
subscribers . forEach { it (groups) }
}
}

View file

@ -0,0 +1,31 @@
package com.kernelmaft.zanbur.common
enum class ChangeSource { Local , Remote }
typealias CurrentSceneSubscriber = ( Group , Scene , ChangeSource ) -> Unit
typealias GroupAddedSubscriber = ( Group , ChangeSource ) -> Unit
object AppState {
private val currentSceneSubscribers : MutableList <CurrentSceneSubscriber> = mutableListOf ()
private val groupAddedSubscribers : MutableList <GroupAddedSubscriber> = mutableListOf ()
fun setCurrentScene ( group : Group , newScene : Scene , source : ChangeSource ) {
for ( subscriber in currentSceneSubscribers ) {
subscriber ( group , newScene , source )
}
}
fun subscribeToCurrentScene ( subscriber : CurrentSceneSubscriber ) {
currentSceneSubscribers . add (subscriber)
}
fun addGroup ( newGroup : Group , source : ChangeSource ) {
for ( subscriber in groupAddedSubscribers ) {
subscriber ( newGroup , source )
}
}
fun subscribeToGroupAdded ( subscriber : GroupAddedSubscriber ) {
groupAddedSubscribers . add (subscriber)
}
}

View file

@ -1,4 +1,4 @@
package com.kernelmaft.zanbur
package com.kernelmaft.zanbur.common

View file

@ -1,4 +1,4 @@
package com.kernelmaft.zanbur
package com.kernelmaft.zanbur.common

View file

@ -1,65 +0,0 @@
package com.kernelmaft.zanbur
import android.os.*
import androidx.activity.compose.*
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.unit.*
import androidx.datastore.preferences.core.*
import androidx.lifecycle.*
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.*
class MainActivity : EdgeToEdgeActivity () {
override fun onCreate ( savedInstanceState : Bundle ? ) {
super . onCreate (savedInstanceState)
Config . groups . forEach { AppState . addGroup (it) }
var groups by mutableStateOf ( AppState . groups )
AppState . subscribe { groups = it }
lifecycleScope . launch (IO) {
val prefs = dataStore . data . firstOrNull ()
if ( prefs != null ) {
val savedSceneName = prefs [ stringPreferencesKey ("scene") ]
if ( savedSceneName != null ) {
val savedScene = AppState . groups [0] . scenes . find { it . name == savedSceneName }
if ( savedScene != null ) {
AppState . setCurrentScene ( 0 , savedScene )
}
}
}
}
setContent {
AppFrame {
Column ( Modifier . width ( 300 . dp ) ) {
groups . forEach { group ->
SceneSwitcher (group) {
AppState . setCurrentScene ( group . id , it )
publishSceneChange ( group , it )
}
}
}
}
}
MqttClient . run (lifecycleScope)
}
override fun onStop () {
super . onStop ()
val currentScene = AppState . groups [0] . currentScene
if ( currentScene != null ) lifecycleScope . launch (IO) {
applicationContext . dataStore . edit {
it [ stringPreferencesKey ("scene") ] = currentScene . name
}
}
}
}

View file

@ -1,16 +1,16 @@
package com.kernelmaft.zanbur
package com.kernelmaft.zanbur.network
import MQTTClient
import com.kernelmaft.zanbur.Config.MQTT_SERVER_HOST
import com.kernelmaft.zanbur.Config.MQTT_SERVER_PORT
import com.kernelmaft.zanbur.Config.MQTT_TOPIC
import com.kernelmaft.zanbur.common.Config.MQTT_SERVER_HOST
import com.kernelmaft.zanbur.common.Config.MQTT_SERVER_PORT
import com.kernelmaft.zanbur.common.Config.MQTT_TOPIC
import io.github.davidepianca98.*
import io.github.davidepianca98.mqtt.*
import io.github.davidepianca98.mqtt.MQTTVersion.*
import io.github.davidepianca98.mqtt.packets.Qos.*
import io.github.davidepianca98.mqtt.packets.mqtt.*
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.serialization.json.*
import mqtt.*
import mqtt.MQTTVersion.*
import mqtt.packets.Qos.*
import mqtt.packets.mqtt.*

View file

@ -1,6 +1,7 @@
package com.kernelmaft.zanbur
package com.kernelmaft.zanbur.network
import com.kernelmaft.zanbur.Config.MQTT_TOPIC
import com.kernelmaft.zanbur.common.*
import com.kernelmaft.zanbur.common.Config.MQTT_TOPIC
import kotlinx.serialization.*
import kotlinx.serialization.json.*

View file

@ -1,10 +0,0 @@
package com.kernelmaft.zanbur
import android.content.*
import androidx.datastore.core.*
import androidx.datastore.preferences.*
import androidx.datastore.preferences.core.*
val Context . dataStore : DataStore <Preferences> by preferencesDataStore ("state")

View file

@ -1,4 +1,4 @@
package com.kernelmaft.zanbur
package com.kernelmaft.zanbur.ui
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement.Center
@ -9,6 +9,7 @@ import androidx.compose.material3.ButtonDefaults.shape
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import com.kernelmaft.zanbur.common.*

View file

@ -0,0 +1,22 @@
package com.kernelmaft.zanbur.ui
import androidx.compose.runtime.*
import com.kernelmaft.zanbur.common.*
fun createGroupsComposeState () : MutableState < List <Group> > {
val groups : MutableState < List <Group> > = mutableStateOf ( emptyList () )
AppState . subscribeToGroupAdded { newGroup , _ ->
groups . value = groups . value . plus (newGroup)
}
AppState . subscribeToCurrentScene { group , newScene , source ->
groups . value = groups . value . map { when ( it . id ) {
group . id -> it . copy ( currentScene = newScene )
else -> it
} }
}
return groups
}

View file

@ -0,0 +1,43 @@
package com.kernelmaft.zanbur.ui
import android.os.*
import androidx.activity.compose.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.*
import androidx.compose.ui.unit.*
import androidx.lifecycle.*
import com.kernelmaft.zanbur.common.*
import com.kernelmaft.zanbur.common.ChangeSource.*
import com.kernelmaft.zanbur.network.*
class MainActivity : EdgeToEdgeActivity () {
override fun onCreate ( savedInstanceState : Bundle ? ) {
super . onCreate (savedInstanceState)
val groups = createGroupsComposeState ()
AppState . subscribeToCurrentScene { group , newScene , source ->
if ( source == Local ) {
publishSceneChange ( group , newScene )
}
}
Config . groups . forEach { AppState . addGroup ( it , Remote ) }
setContent {
AppFrame {
Column ( Modifier . width ( 300 . dp ) ) {
groups . value . forEach { group ->
SceneSwitcher (group) { newScene ->
AppState . setCurrentScene ( group , newScene , Local )
}
}
}
}
}
MqttClient . run (lifecycleScope)
}
}

View file

@ -1,4 +1,4 @@
package com.kernelmaft.zanbur
package com.kernelmaft.zanbur.ui
import android.os.*
import androidx.activity.*
@ -29,7 +29,6 @@ open class EdgeToEdgeActivity : ComponentActivity () {
override fun onCreate ( savedInstanceState : Bundle ? ) {
super . onCreate (savedInstanceState)
enableEdgeToEdge ()
actionBar ?. hide ()
}
}

View file

@ -1,6 +0,0 @@
<resources>
<string name = "app_name" >Zanbur</string>
<string name = "title_activity_main" >Zanbur</string>
</resources>

View file

@ -1,8 +0,0 @@
<resources>
<style
name = "Theme.Zanbur"
parent = "Theme.Material3.DayNight" >
</style>
</resources>