This commit is contained in:
Laurenz 2024-09-03 12:25:13 +02:00
parent af503c04f8
commit fc1a8e0367
Signed by: C0ffeeCode
SSH key fingerprint: SHA256:jnEltBNftC3wUZESLSMvM9zVPOkkevGRzqqoW2k2ORI
9 changed files with 135 additions and 60 deletions

View file

@ -1,6 +1,34 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />

24
.vscode/launch.json vendored
View file

@ -1,24 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "android",
"request": "launch",
"name": "Android launch",
"appSrcRoot": "${workspaceRoot}/app/src/main",
"apkFile": "${workspaceRoot}/app/build/outputs/apk/debug/app-debug.apk",
"adbPort": 5037
},
{
"type": "android",
"request": "attach",
"name": "Android attach",
"appSrcRoot": "${workspaceRoot}/app/src/main",
"adbPort": 5037,
"processId": "${command:PickAndroidProcess}"
}
]
}

View file

@ -76,6 +76,7 @@ dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.datastore.preferences)
implementation(libs.ktor.client.okhttp)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

View file

@ -59,8 +59,8 @@ class MainActivity : ComponentActivity() {
val cf = obtainClient(ctx)
runBlocking {
homeClient = cf.first()
homeClient!!.wsClient.connect()
}
homeClient!!.connect(rememberCoroutineScope())
}
val onLoginSubmit: suspend (String, String) -> Unit = { url: String, token: String ->

View file

@ -5,10 +5,12 @@ 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
@ -19,23 +21,33 @@ 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(Json { // isLenient = true
// useArrayPolymorphism = true
allowStructuredMapKeys = true
ignoreUnknownKeys = true // explicitNulls = false
// namingStrategy = JsonNamingStrategy.SnakeCase
})
json(jsonConfig)
}
install(DefaultRequest) {
@ -43,6 +55,8 @@ class HomeClient(private val baseUrl: String, private val accessToken: String) {
}
}
val wsClient = WSClient(GlobalScope, baseUrl, accessToken, client)
suspend fun apiStatus(): HttpResponse {
val res = client.get("$baseUrl/api/")
return res
@ -76,27 +90,6 @@ class HomeClient(private val baseUrl: String, private val accessToken: String) {
}
val res = updateService(entityId, "switch", service)
}
fun connect(coroutineScope: CoroutineScope) {
GlobalScope.launch {
client.webSocket(
"${baseUrl.replace("http", "ws")}/api/websocket"
) {
var iteratingNumber = 0
var isReady = false
while (true) {
val data = receiveDeserialized<WebSocketIncoming>()
if (data.type == "auth_required") sendSerialized(WSAuth("auth", accessToken))
if (data.type == "auth_ok") {
isReady = true
sendSerialized(WSSubscribe(iteratingNumber, "state_changed"))
}
println(data)
iteratingNumber++
}
}
}
}
}
val switchServices = listOf("turn_off", "turn_on", "toggle")

View file

@ -1,5 +1,6 @@
package dev.coffeeco.homenative.data
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -21,7 +22,8 @@ data class WSAuth(
)
@Serializable
abstract class WebSocketMessage {
sealed class WebSocketMessage {
@EncodeDefault
@SerialName("type")
abstract val type: String
@ -30,11 +32,16 @@ abstract class WebSocketMessage {
}
@Serializable
data class WSSubscribe (
data class WSSubscribe(
@SerialName("id")
override val id: Int,
// @EncodeDefault
// @SerialName("type")
// val type: String = "subscribe_events",
@SerialName("event_type")
val eventType: String?
): WebSocketMessage() {
override val type: String
get() = "subscribe_events"
val eventType: String? = null,
) : WebSocketMessage() {
@EncodeDefault
@SerialName("type")
override val type: String = "subscribe_events"
}

View file

@ -0,0 +1,64 @@
package dev.coffeeco.homenative.data
import io.ktor.client.HttpClient
import io.ktor.client.plugins.websocket.receiveDeserialized
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,
) {
private val wsUrl = "${baseUrl.replace("http", "ws")}/api/websocket"
private var session: DefaultWebSocketSession? = null
private var iteratingNumber = 0
suspend fun connect() {
GlobalScope.launch {
httpClient.webSocket(wsUrl) {
session = this
while (true) {
try {
val data = receiveDeserialized<WebSocketIncoming>()
when (data.type) {
"auth_required" -> {
println("WebSocket: Authentication required")
sendSerialized(WSAuth("auth", accessToken))
}
"auth_ok" -> {
println("WebSocket: Authentication OK")
val data1 = WSSubscribe(iteratingNumber)
val serialized = Json.encodeToString(data1)
// send(serialized)
sendSerialized(data1)
}
else -> {
println("WebSocket: Misc data received: $data")
}
}
iteratingNumber++
} catch (e: Exception) {
println("WS Exception: $e")
}
}
}
}
}
suspend fun send_dunnschiss() {
while (session == null) {
}
session!!.send("DÜNNSCHISS")
}
}

View file

@ -31,9 +31,10 @@ fun ControllingCard(homeClient: HomeClient, state: StatesResItem) {
modifier = Modifier
.height(150.dp)
.clickable {
println("hi")
println("Button clicked!")
coroutineScope.launch {
homeClient.updateSwitch(state.entityId, "toggle")
// homeClient.wsClient.send_dunnschiss()
}
},
colors = CardDefaults.cardColors(

View file

@ -13,6 +13,8 @@ navigationCompose = "2.7.7"
ktorClient = "3.0.0-beta-2"
kotlinxSerializationJson = "1.7.2"
datastorePreferences = "1.1.1"
hilt = "1.2.0"
dagger = "2.51.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -32,15 +34,18 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
ktor-client-websockets = { group = "io.ktor", name = "ktor-client-websockets", version.ref = "ktorClient" }
ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktorClient" }
ktor-client-serialization = { group = "io.ktor", name = "ktor-client-serialization", version.ref = "ktorClient" }
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktorClient" }
ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktorClient" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktorClient" }
hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hilt" }
hilt-navcompose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt" }
hilt-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hilt" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
dagger = { id = "com.google.dagger.hilt.android", version.ref = "dagger" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }