This commit is contained in:
Laurenz 2024-09-03 13:17:05 +02:00
parent fc1a8e0367
commit 89ea5df36e
Signed by: C0ffeeCode
SSH key fingerprint: SHA256:jnEltBNftC3wUZESLSMvM9zVPOkkevGRzqqoW2k2ORI
7 changed files with 110 additions and 124 deletions

View file

@ -21,6 +21,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
@ -29,13 +30,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.lifecycleScope
import dev.coffeeco.homenative.data.HomeClient
import dev.coffeeco.homenative.data.obtainClient
import dev.coffeeco.homenative.data.setInstance
import dev.coffeeco.homenative.ui.ControlContainer
import dev.coffeeco.homenative.ui.SignInUI
import dev.coffeeco.homenative.ui.theme.HomeNativeTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class MainActivity : ComponentActivity() {
@ -55,13 +59,15 @@ class MainActivity : ComponentActivity() {
@Composable
private fun AppContainer() {
val ctx = LocalContext.current
LaunchedEffect(Unit) {
if (homeClient == null) {
val cf = obtainClient(ctx)
runBlocking {
lifecycleScope.launch(Dispatchers.IO) {
homeClient = cf.first()
homeClient!!.wsClient.connect()
}
}
}
val onLoginSubmit: suspend (String, String) -> Unit = { url: String, token: String ->
setInstance(ctx, url, token, refreshToken = null)

View file

@ -5,12 +5,7 @@ import io.ktor.client.call.body
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.plugins.websocket.receiveDeserialized
import io.ktor.client.plugins.websocket.sendSerialized
import io.ktor.client.plugins.websocket.webSocket
import io.ktor.client.plugins.websocket.webSocketSession
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
@ -21,33 +16,21 @@ import io.ktor.http.HttpHeaders
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter
import io.ktor.serialization.kotlinx.json.json
import io.ktor.utils.io.InternalAPI
import io.ktor.websocket.DefaultWebSocketSession
import io.ktor.websocket.FrameType
import io.ktor.websocket.send
import io.ktor.websocket.serialization.receiveDeserializedBase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import java.util.EventListener
class HomeClient(private val baseUrl: String, private val accessToken: String) {
val jsonConfig = Json { // isLenient = true
// useArrayPolymorphism = true
allowStructuredMapKeys = true
ignoreUnknownKeys = true // explicitNulls = false
// namingStrategy = JsonNamingStrategy.SnakeCase
encodeDefaults = true // IMPORTANT
}
private val client: HttpClient = HttpClient(OkHttp) {
install(WebSockets) {
contentConverter = KotlinxWebsocketSerializationConverter(Json)
}
install(ContentNegotiation) {
json(jsonConfig)
json(Json { // isLenient = true
// useArrayPolymorphism = true
allowStructuredMapKeys = true
ignoreUnknownKeys = true // explicitNulls = false
// namingStrategy = JsonNamingStrategy.SnakeCase
encodeDefaults = true
})
}
install(DefaultRequest) {
@ -55,7 +38,7 @@ class HomeClient(private val baseUrl: String, private val accessToken: String) {
}
}
val wsClient = WSClient(GlobalScope, baseUrl, accessToken, client)
val wsClient = WSClient(baseUrl, accessToken, client)
suspend fun apiStatus(): HttpResponse {
val res = client.get("$baseUrl/api/")

View file

@ -0,0 +1,9 @@
package dev.coffeeco.homenative.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class PostServiceBody(
@SerialName("entity_id") val entityId: String
)

View file

@ -2,7 +2,6 @@ package dev.coffeeco.homenative.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
data class AuthRes(
@ -45,50 +44,3 @@ data class StatesResItemContext(
@SerialName("parent_id") val parentId: String?,
@SerialName("user_id") val userId: String?
)
@Serializable
sealed class WebSocketIncoming {
@SerialName("type")
abstract val type: String
}
@Serializable
@SerialName("auth_required")
data class WebSocketAuthRequired(
@SerialName("ha_version")
val homeAssistantVersion: String,
) : WebSocketIncoming() {
override val type: String
get() = "auth_required"
}
@Serializable
@SerialName("auth_ok")
data class WebSocketAuthOk(
@SerialName("ha_version")
val homeAssistantVersion: String
) : WebSocketIncoming() {
override val type: String
get() = "auth_ok"
}
@Serializable
@SerialName("auth_invalid")
data class WebSocketAuthInvalid(
val message: String
) : WebSocketIncoming() {
override val type: String
get() = "auth_invalid"
}
@Serializable
@SerialName("result")
data class WebSocketResult(
val id: Int,
val success: Boolean,
val result: JsonObject? = null,
val error: JsonObject? = null
) : WebSocketIncoming() {
override val type: String
get() = "result"
}

View file

@ -6,14 +6,10 @@ import io.ktor.client.plugins.websocket.sendSerialized
import io.ktor.client.plugins.websocket.webSocket
import io.ktor.websocket.DefaultWebSocketSession
import io.ktor.websocket.send
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
class WSClient(
private val coroutineScope: CoroutineScope,
private val baseUrl: String,
private val accessToken: String,
private val httpClient: HttpClient,
@ -23,7 +19,6 @@ class WSClient(
private var iteratingNumber = 0
suspend fun connect() {
GlobalScope.launch {
httpClient.webSocket(wsUrl) {
session = this
while (true) {
@ -39,7 +34,6 @@ class WSClient(
println("WebSocket: Authentication OK")
val data1 = WSSubscribe(iteratingNumber)
val serialized = Json.encodeToString(data1)
// send(serialized)
sendSerialized(data1)
}
@ -54,7 +48,6 @@ class WSClient(
}
}
}
}
suspend fun send_dunnschiss() {
while (session == null) {

View file

@ -0,0 +1,52 @@
package dev.coffeeco.homenative.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
sealed class WebSocketIncoming {
@SerialName("type")
abstract val type: String
}
@Serializable
@SerialName("auth_required")
data class WebSocketAuthRequired(
@SerialName("ha_version")
val homeAssistantVersion: String,
) : WebSocketIncoming() {
override val type: String
get() = "auth_required"
}
@Serializable
@SerialName("auth_ok")
data class WebSocketAuthOk(
@SerialName("ha_version")
val homeAssistantVersion: String
) : WebSocketIncoming() {
override val type: String
get() = "auth_ok"
}
@Serializable
@SerialName("auth_invalid")
data class WebSocketAuthInvalid(
val message: String
) : WebSocketIncoming() {
override val type: String
get() = "auth_invalid"
}
@Serializable
@SerialName("result")
data class WebSocketResult(
val id: Int,
val success: Boolean,
val result: JsonObject? = null,
val error: JsonObject? = null
) : WebSocketIncoming() {
override val type: String
get() = "result"
}

View file

@ -4,11 +4,6 @@ import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class PostServiceBody(
@SerialName("entity_id") val entityId: String
)
@Serializable
/**
* Special since `id` field must be omitted in contrast to [WebSocketMessage.id]
@ -22,11 +17,14 @@ data class WSAuth(
)
@Serializable
sealed class WebSocketMessage {
/**
* Generic format for outgoing messages
*/
sealed class WebSocketMessage(
@EncodeDefault
@SerialName("type")
abstract val type: String
val type: String,
) {
@SerialName("id")
abstract val id: Int
}
@ -35,13 +33,6 @@ sealed class WebSocketMessage {
data class WSSubscribe(
@SerialName("id")
override val id: Int,
// @EncodeDefault
// @SerialName("type")
// val type: String = "subscribe_events",
@SerialName("event_type")
val eventType: String? = null,
) : WebSocketMessage() {
@EncodeDefault
@SerialName("type")
override val type: String = "subscribe_events"
}
) : WebSocketMessage(type = "subscribe_events")