feat: add logic to create integration using code

This commit is contained in:
Fritz Heiden 2025-03-19 14:41:10 +01:00
parent 035393578f
commit 7a871f9fa4
13 changed files with 112 additions and 49 deletions

View File

@ -50,6 +50,8 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)

View File

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"

View File

@ -1,9 +0,0 @@
package com.example.tvcontroller
import android.util.Log
object DeviceService {
fun createIntegration(name: String, code: String) {
Log.i("DeviceService", "Creating integration for $name with code $code")
}
}

View File

@ -14,10 +14,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -31,8 +29,8 @@ import com.example.tvcontroller.ui.theme.TVControllerTheme
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.tvcontroller.services.BluetoothService
import com.example.tvcontroller.ui.AppViewModel import com.example.tvcontroller.ui.AppViewModel

View File

@ -1,6 +1,5 @@
package com.example.tvcontroller package com.example.tvcontroller
import android.util.Log
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -13,10 +12,6 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -25,13 +20,7 @@ import com.example.tvcontroller.ui.AppViewModel
@Composable @Composable
fun SettingsScreen(appViewModel: AppViewModel = viewModel()) { fun SettingsScreen(appViewModel: AppViewModel = viewModel()) {
var serverUrl by remember { mutableStateOf("") } val viewModel = viewModel<SettingsViewModel>()
var deviceName by remember { mutableStateOf(android.os.Build.MANUFACTURER + " " + android.os.Build.MODEL) }
var registrationCode by remember { mutableStateOf("") }
fun connect() {
Log.i("SettingsScreen", "Save settings: $serverUrl, $deviceName, $registrationCode")
}
Column( Column(
modifier = Modifier modifier = Modifier
@ -54,23 +43,23 @@ fun SettingsScreen(appViewModel: AppViewModel = viewModel()) {
) )
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = serverUrl, value = viewModel.serverAddress,
onValueChange = { serverUrl = it }, onValueChange = viewModel::onServerAddressChanged,
label = { Text(stringResource(id = R.string.server_url_label)) } label = { Text(stringResource(id = R.string.server_address_label)) }
) )
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = deviceName, value = viewModel.deviceName,
onValueChange = { deviceName = it }, onValueChange = viewModel::onDeviceNameChanged,
label = { Text(stringResource(id = R.string.device_name_label)) } label = { Text(stringResource(id = R.string.device_name_label)) }
) )
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = registrationCode, value = viewModel.registrationCode,
onValueChange = { registrationCode = it }, onValueChange = viewModel::onRegistrationCodeChanged,
label = { Text(stringResource(id = R.string.registration_code_label)) } label = { Text(stringResource(id = R.string.registration_code_label)) }
) )
OutlinedButton(onClick = { connect() }, modifier = Modifier.fillMaxWidth()) { OutlinedButton(onClick = { viewModel.connect() }, modifier = Modifier.fillMaxWidth()) {
Text( Text(
stringResource(id = R.string.connect_button_label) stringResource(id = R.string.connect_button_label)
) )
@ -81,9 +70,15 @@ fun SettingsScreen(appViewModel: AppViewModel = viewModel()) {
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall
) )
if (appViewModel.isBluetoothEnabled()) { if (appViewModel.isBluetoothEnabled()) {
Text(text = "Controller settings: Bluetooth is enabled.", style = MaterialTheme.typography.bodyMedium) Text(
text = "Controller settings: Bluetooth is enabled.",
style = MaterialTheme.typography.bodyMedium
)
} else { } else {
Text(text = "Bluetooth is disabled. Please enable it in settings.", style = MaterialTheme.typography.bodyMedium) Text(
text = "Bluetooth is disabled. Please enable it in settings.",
style = MaterialTheme.typography.bodyMedium
)
} }
} }
} }

View File

@ -0,0 +1,38 @@
package com.example.tvcontroller
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.tvcontroller.services.DeviceService
import kotlinx.coroutines.launch
class SettingsViewModel : ViewModel() {
var serverAddress by mutableStateOf(DeviceService.getServerAddress())
private set
var deviceName by mutableStateOf(android.os.Build.MANUFACTURER + " " + android.os.Build.MODEL)
private set
var registrationCode by mutableStateOf("")
private set
fun connect() {
//Log.i("SettingsScreen", "Save settings: $serverUrl, $deviceName, $registrationCode")
viewModelScope.launch {
DeviceService.setServerAddress(serverAddress)
DeviceService.createIntegration(deviceName, registrationCode)
}
}
fun onServerAddressChanged(url: String) {
serverAddress = url
}
fun onDeviceNameChanged(name: String) {
deviceName = name
}
fun onRegistrationCodeChanged(code: String) {
registrationCode = code
}
}

View File

@ -1,14 +1,11 @@
package com.example.tvcontroller package com.example.tvcontroller.services
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.app.Activity
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.IntentFilter import android.content.IntentFilter
import androidx.activity.result.ActivityResultLauncher
import androidx.core.app.ComponentActivity
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
class BluetoothService(private val context: Context) { class BluetoothService(private val context: Context) {

View File

@ -0,0 +1,46 @@
package com.example.tvcontroller.services
import android.util.Log
import io.ktor.client.engine.cio.*
import io.ktor.client.*
import io.ktor.client.call.body
import io.ktor.client.request.headers
import io.ktor.client.request.request
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod
import org.json.JSONObject
object DeviceService {
private var client = HttpClient(CIO)
private var serverAddress: String = ""
suspend fun createIntegration(name: String, code: String) {
Log.i("DeviceService", "Creating integration for $name with code $code at $serverAddress")
val json = JSONObject()
json.put("name", name)
json.put("code", code)
try {
val response: HttpResponse = client.request("http://$serverAddress/api/integrations") {
method = HttpMethod.Post
setBody(json.toString())
headers {
append("Content-Type", "application/json")
}
}
val body: String = response.body()
Log.i("DeviceService", "Response: ${response.status.value} $body")
} catch (e: Exception) {
Log.e("DeviceService", "Error creating integration", e)
}
}
fun setServerAddress(url: String) {
serverAddress = url
}
fun getServerAddress(): String {
return serverAddress
}
}

View File

@ -3,5 +3,5 @@ package com.example.tvcontroller.ui
import com.example.tvcontroller.Screen import com.example.tvcontroller.Screen
data class AppUiState( data class AppUiState(
val currentScreen: Screen = Screen.Camera, val currentScreen: Screen = Screen.Settings,
) )

View File

@ -1,16 +1,7 @@
package com.example.tvcontroller.ui package com.example.tvcontroller.ui
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.example.tvcontroller.BluetoothService
import com.example.tvcontroller.Screen
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class AppViewModel() : ViewModel() { class AppViewModel() : ViewModel() {
private var isBluetoothEnabled = mutableStateOf(false) private var isBluetoothEnabled = mutableStateOf(false)

View File

@ -10,4 +10,5 @@
<string name="connection_state_disconnected">disconnected</string> <string name="connection_state_disconnected">disconnected</string>
<string name="save_button_label">Save</string> <string name="save_button_label">Save</string>
<string name="connect_button_label">Connect</string> <string name="connect_button_label">Connect</string>
<string name="server_address_label">Server address</string>
</resources> </resources>

View File

@ -20,4 +20,4 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the # Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true

View File

@ -5,6 +5,7 @@ coreKtx = "1.10.1"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" junitVersion = "1.1.5"
espressoCore = "3.5.1" espressoCore = "3.5.1"
ktor = "3.1.0"
lifecycleRuntimeKtx = "2.6.1" lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0" activityCompose = "1.8.0"
composeBom = "2024.04.01" composeBom = "2024.04.01"
@ -26,6 +27,8 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }