From 5db2caeaa453478fdca40799ecd8ac36d8732cdc Mon Sep 17 00:00:00 2001 From: Fritz Heiden Date: Mon, 31 Mar 2025 13:26:42 +0200 Subject: [PATCH] feat: properly register device and keep token as cookie --- .../tvcontroller/services/DeviceService.kt | 14 ++- .../tvcontroller/ui/views/SettingsView.kt | 101 +++++++++--------- .../{ => ui/views}/SettingsViewModel.kt | 27 +++-- app/src/main/res/raw/samsung.csv | 39 ------- 4 files changed, 79 insertions(+), 102 deletions(-) rename app/src/main/java/com/example/tvcontroller/{ => ui/views}/SettingsViewModel.kt (80%) delete mode 100644 app/src/main/res/raw/samsung.csv diff --git a/app/src/main/java/com/example/tvcontroller/services/DeviceService.kt b/app/src/main/java/com/example/tvcontroller/services/DeviceService.kt index e5960e5..38c51ff 100644 --- a/app/src/main/java/com/example/tvcontroller/services/DeviceService.kt +++ b/app/src/main/java/com/example/tvcontroller/services/DeviceService.kt @@ -7,18 +7,20 @@ import com.example.tvcontroller.data.Integration import io.ktor.client.engine.cio.* import io.ktor.client.* import io.ktor.client.call.body +import io.ktor.client.plugins.cookies.HttpCookies 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.HeadersBuilder import io.ktor.http.HttpMethod import org.json.JSONObject private const val SHARED_PREFERENCES_NAME = "devices"; class DeviceService(private val context: Context) { - private var client = HttpClient(CIO) + private var client = HttpClient(CIO) { + install(HttpCookies) + } private var serverAddress: String = "" private var token: String = "" private var deviceId: String = "" @@ -27,13 +29,15 @@ class DeviceService(private val context: Context) { loadPreferences() } - suspend fun createIntegration(name: String, code: String) { + suspend fun registerIntegration(name: String, code: String) { Log.i("DeviceService", "Creating integration for $name with code $code at $serverAddress") val requestJson = JSONObject() requestJson.put("name", name) requestJson.put("code", code) + token = "" + deviceId = "" try { - val response: HttpResponse = client.request("http://$serverAddress/api/integrations") { + val response: HttpResponse = client.request("http://$serverAddress/api/integrations/register") { method = HttpMethod.Post setBody(requestJson.toString()) headers { @@ -54,7 +58,7 @@ class DeviceService(private val context: Context) { Log.i("DeviceService", "Response: ${response.status.value} $body") } catch (e: Exception) { - Log.e("DeviceService", "Error creating integration", e) + Log.e("DeviceService", "Error registering integration", e) } } diff --git a/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt b/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt index 4df031f..bdd4bf1 100644 --- a/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt +++ b/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -31,22 +32,24 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.example.tvcontroller.R import com.example.tvcontroller.Settings -import com.example.tvcontroller.SettingsViewModel -import com.example.tvcontroller.SettingsViewModel.Companion.CONNECT_CONTROLLER_VIEW -import com.example.tvcontroller.SettingsViewModel.Companion.MAIN_SETTINGS_VIEW +import com.example.tvcontroller.ui.views.SettingsViewModel +import com.example.tvcontroller.ui.views.SettingsViewModel.Companion.CONNECT_CONTROLLER_VIEW +import com.example.tvcontroller.ui.views.SettingsViewModel.Companion.MAIN_SETTINGS_VIEW import com.example.tvcontroller.services.BluetoothService import com.example.tvcontroller.services.ControllerService import com.example.tvcontroller.services.DeviceService +import com.example.tvcontroller.webrtc.RtcPeerConnection @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsView( deviceService: DeviceService, - bluetoothService: BluetoothService + bluetoothService: BluetoothService, + rtcPeerConnection: RtcPeerConnection ) { val viewModel = viewModel( factory = SettingsViewModel.provideFactory( - deviceService, bluetoothService + deviceService, bluetoothService, rtcPeerConnection ) ) val navController = rememberNavController() @@ -102,25 +105,29 @@ fun SettingsView( text = "Controller status: " + getBluetoothConnectionStateString(viewModel.bluetoothConnectionState) + ".", style = MaterialTheme.typography.bodyMedium ) - if (viewModel.bluetoothConnectionState == BluetoothService.STATE_CONNECTED) - OutlinedButton( - onClick = viewModel::disconnectBluetoothDevice, - modifier = Modifier.fillMaxWidth() - ) { - Text( - stringResource(id = R.string.disconnect_button_label) - ) - } - else - OutlinedButton( - onClick = { navController.navigate(CONNECT_CONTROLLER_VIEW.toString()) }, - enabled = viewModel.bluetoothConnectionState != BluetoothService.STATE_OFF, - modifier = Modifier.fillMaxWidth() - ) { - Text( - stringResource(id = R.string.connect_button_label) - ) - } + if (viewModel.bluetoothConnectionState == BluetoothService.STATE_CONNECTED) OutlinedButton( + onClick = viewModel::disconnectBluetoothDevice, modifier = Modifier.fillMaxWidth() + ) { + Text( + stringResource(id = R.string.disconnect_button_label) + ) + } + else OutlinedButton( + onClick = { navController.navigate(CONNECT_CONTROLLER_VIEW.toString()) }, + enabled = viewModel.bluetoothConnectionState != BluetoothService.STATE_OFF, + modifier = Modifier.fillMaxWidth() + ) { + Text( + stringResource(id = R.string.connect_button_label) + ) + } + + Button( + onClick = { viewModel.connectRtcPeerConnection() }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = "Connect RTC Peer") + } } } @@ -151,33 +158,25 @@ fun SettingsView( ) pairedDevices.value.forEach { device -> if (device == null) return - ListItem( - headlineContent = { Text(device.getName()) }, - supportingContent = { - if (device.getName() != device.getAddress()) Text( - device.getAddress() - ) - }, - modifier = Modifier.clickable(onClick = { - viewModel.connectBluetoothDevice( - device - ) - }), - trailingContent = { - if (device == viewModel.currentBluetoothDevice) - if (viewModel.bluetoothConnectionState == BluetoothService.STATE_CONNECTED) - Icon( - painterResource(id = R.drawable.baseline_check_24), - contentDescription = "state" - ) - else - CircularProgressIndicator( - modifier = Modifier.width(16.dp), - color = MaterialTheme.colorScheme.secondary, - trackColor = MaterialTheme.colorScheme.surfaceVariant, - ) - } - ) + ListItem(headlineContent = { Text(device.getName()) }, supportingContent = { + if (device.getName() != device.getAddress()) Text( + device.getAddress() + ) + }, modifier = Modifier.clickable(onClick = { + viewModel.connectBluetoothDevice( + device + ) + }), trailingContent = { + if (device == viewModel.currentBluetoothDevice) if (viewModel.bluetoothConnectionState == BluetoothService.STATE_CONNECTED) Icon( + painterResource(id = R.drawable.baseline_check_24), + contentDescription = "state" + ) + else CircularProgressIndicator( + modifier = Modifier.width(16.dp), + color = MaterialTheme.colorScheme.secondary, + trackColor = MaterialTheme.colorScheme.surfaceVariant, + ) + }) } } } diff --git a/app/src/main/java/com/example/tvcontroller/SettingsViewModel.kt b/app/src/main/java/com/example/tvcontroller/ui/views/SettingsViewModel.kt similarity index 80% rename from app/src/main/java/com/example/tvcontroller/SettingsViewModel.kt rename to app/src/main/java/com/example/tvcontroller/ui/views/SettingsViewModel.kt index 888fb1b..8e98c72 100644 --- a/app/src/main/java/com/example/tvcontroller/SettingsViewModel.kt +++ b/app/src/main/java/com/example/tvcontroller/ui/views/SettingsViewModel.kt @@ -1,24 +1,29 @@ -package com.example.tvcontroller +package com.example.tvcontroller.ui.views +import android.os.Build +import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import com.example.tvcontroller.Settings import com.example.tvcontroller.data.BluetoothDevice import com.example.tvcontroller.services.BluetoothService import com.example.tvcontroller.services.DeviceService +import com.example.tvcontroller.webrtc.RtcPeerConnection import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class SettingsViewModel( private val deviceService: DeviceService, - private val bluetoothService: BluetoothService + private val bluetoothService: BluetoothService, + private val rtcPeerConnection: RtcPeerConnection ) : ViewModel() { var serverAddress by mutableStateOf(deviceService.getServerAddress()) private set - var deviceName by mutableStateOf(android.os.Build.MANUFACTURER + " " + android.os.Build.MODEL) + var deviceName by mutableStateOf(Build.MANUFACTURER + " " + Build.MODEL) private set var registrationCode by mutableStateOf("") private set @@ -46,15 +51,22 @@ class SettingsViewModel( //Log.i("SettingsScreen", "Save settings: $serverUrl, $deviceName, $registrationCode") viewModelScope.launch { deviceService.setServerAddress(serverAddress) - deviceService.createIntegration(deviceName, registrationCode) + deviceService.registerIntegration(deviceName, registrationCode) updateConnectionState() + updateDeviceInfo() + } + } + + fun connectRtcPeerConnection() { + viewModelScope.launch(Dispatchers.IO) { + rtcPeerConnection.connect() } } private fun updateConnectionState() { + Log.i("SettingsViewModel", "Device token: ${deviceService.getToken()}") connectionState = if (deviceService.getToken().isEmpty()) { Settings.ConnectionState.Unregistered - return } else { Settings.ConnectionState.Registered } @@ -100,11 +112,12 @@ class SettingsViewModel( fun provideFactory( deviceService: DeviceService, - bluetoothService: BluetoothService + bluetoothService: BluetoothService, + rtcPeerConnection: RtcPeerConnection ) = object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return SettingsViewModel(deviceService, bluetoothService) as T + return SettingsViewModel(deviceService, bluetoothService, rtcPeerConnection) as T } } } diff --git a/app/src/main/res/raw/samsung.csv b/app/src/main/res/raw/samsung.csv deleted file mode 100644 index 767f603..0000000 --- a/app/src/main/res/raw/samsung.csv +++ /dev/null @@ -1,39 +0,0 @@ -functionname,protocol,device,subdevice,function -INPUT SOURCE,NECx2,7,7,1 -POWER,NECx2,7,7,2 -1,NECx2,7,7,4 -2,NECx2,7,7,5 -3,NECx2,7,7,6 -VOLUME +,NECx2,7,7,7 -4,NECx2,7,7,8 -5,NECx2,7,7,9 -6,NECx2,7,7,10 -VOLUME -,NECx2,7,7,11 -7,NECx2,7,7,12 -8,NECx2,7,7,13 -9,NECx2,7,7,14 -MUTE,NECx2,7,7,15 -CHANNEL -,NECx2,7,7,16 -0,NECx2,7,7,17 -CHANNEL +,NECx2,7,7,18 -LAST,NECx2,7,7,19 -MENU,NECx2,7,7,26 -INFO,NECx2,7,7,31 -AD/SUBT,NECx2,7,7,37 -EXIT,NECx2,7,7,45 -E-MANUAL,NECx2,7,7,63 -TOOLS,NECx2,7,7,75 -GUIDE,NECx2,7,7,79 -RETURN,NECx2,7,7,88 -CURSOR UP,NECx2,7,7,96 -CURSOR DOWN,NECx2,7,7,97 -CURSOR RIGHT,NECx2,7,7,98 -CURSOR LEFT,NECx2,7,7,101 -ENTER,NECx2,7,7,104 -CH LIST,NECx2,7,7,107 -SMART HUB,NECx2,7,7,121 -3D,NECx2,7,7,159 -HDMI2,NECx2,7,7,190 -HDMI3,NECx2,7,7,194 -HDMI4,NECx2,7,7,197 -HDMI1,NECx2,7,7,233