feat: properly register device and keep token as cookie

This commit is contained in:
Fritz Heiden 2025-03-31 13:26:42 +02:00
parent cba125738d
commit 5db2caeaa4
4 changed files with 79 additions and 102 deletions

View File

@ -7,18 +7,20 @@ import com.example.tvcontroller.data.Integration
import io.ktor.client.engine.cio.* import io.ktor.client.engine.cio.*
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.client.request.headers import io.ktor.client.request.headers
import io.ktor.client.request.request import io.ktor.client.request.request
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.HeadersBuilder
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import org.json.JSONObject import org.json.JSONObject
private const val SHARED_PREFERENCES_NAME = "devices"; private const val SHARED_PREFERENCES_NAME = "devices";
class DeviceService(private val context: Context) { class DeviceService(private val context: Context) {
private var client = HttpClient(CIO) private var client = HttpClient(CIO) {
install(HttpCookies)
}
private var serverAddress: String = "" private var serverAddress: String = ""
private var token: String = "" private var token: String = ""
private var deviceId: String = "" private var deviceId: String = ""
@ -27,13 +29,15 @@ class DeviceService(private val context: Context) {
loadPreferences() 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") Log.i("DeviceService", "Creating integration for $name with code $code at $serverAddress")
val requestJson = JSONObject() val requestJson = JSONObject()
requestJson.put("name", name) requestJson.put("name", name)
requestJson.put("code", code) requestJson.put("code", code)
token = ""
deviceId = ""
try { try {
val response: HttpResponse = client.request("http://$serverAddress/api/integrations") { val response: HttpResponse = client.request("http://$serverAddress/api/integrations/register") {
method = HttpMethod.Post method = HttpMethod.Post
setBody(requestJson.toString()) setBody(requestJson.toString())
headers { headers {
@ -54,7 +58,7 @@ class DeviceService(private val context: Context) {
Log.i("DeviceService", "Response: ${response.status.value} $body") Log.i("DeviceService", "Response: ${response.status.value} $body")
} catch (e: Exception) { } catch (e: Exception) {
Log.e("DeviceService", "Error creating integration", e) Log.e("DeviceService", "Error registering integration", e)
} }
} }

View File

@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -31,22 +32,24 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.tvcontroller.R import com.example.tvcontroller.R
import com.example.tvcontroller.Settings import com.example.tvcontroller.Settings
import com.example.tvcontroller.SettingsViewModel import com.example.tvcontroller.ui.views.SettingsViewModel
import com.example.tvcontroller.SettingsViewModel.Companion.CONNECT_CONTROLLER_VIEW import com.example.tvcontroller.ui.views.SettingsViewModel.Companion.CONNECT_CONTROLLER_VIEW
import com.example.tvcontroller.SettingsViewModel.Companion.MAIN_SETTINGS_VIEW import com.example.tvcontroller.ui.views.SettingsViewModel.Companion.MAIN_SETTINGS_VIEW
import com.example.tvcontroller.services.BluetoothService import com.example.tvcontroller.services.BluetoothService
import com.example.tvcontroller.services.ControllerService import com.example.tvcontroller.services.ControllerService
import com.example.tvcontroller.services.DeviceService import com.example.tvcontroller.services.DeviceService
import com.example.tvcontroller.webrtc.RtcPeerConnection
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsView( fun SettingsView(
deviceService: DeviceService, deviceService: DeviceService,
bluetoothService: BluetoothService bluetoothService: BluetoothService,
rtcPeerConnection: RtcPeerConnection
) { ) {
val viewModel = viewModel<SettingsViewModel>( val viewModel = viewModel<SettingsViewModel>(
factory = SettingsViewModel.provideFactory( factory = SettingsViewModel.provideFactory(
deviceService, bluetoothService deviceService, bluetoothService, rtcPeerConnection
) )
) )
val navController = rememberNavController() val navController = rememberNavController()
@ -102,17 +105,14 @@ fun SettingsView(
text = "Controller status: " + getBluetoothConnectionStateString(viewModel.bluetoothConnectionState) + ".", text = "Controller status: " + getBluetoothConnectionStateString(viewModel.bluetoothConnectionState) + ".",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
if (viewModel.bluetoothConnectionState == BluetoothService.STATE_CONNECTED) if (viewModel.bluetoothConnectionState == BluetoothService.STATE_CONNECTED) OutlinedButton(
OutlinedButton( onClick = viewModel::disconnectBluetoothDevice, modifier = Modifier.fillMaxWidth()
onClick = viewModel::disconnectBluetoothDevice,
modifier = Modifier.fillMaxWidth()
) { ) {
Text( Text(
stringResource(id = R.string.disconnect_button_label) stringResource(id = R.string.disconnect_button_label)
) )
} }
else else OutlinedButton(
OutlinedButton(
onClick = { navController.navigate(CONNECT_CONTROLLER_VIEW.toString()) }, onClick = { navController.navigate(CONNECT_CONTROLLER_VIEW.toString()) },
enabled = viewModel.bluetoothConnectionState != BluetoothService.STATE_OFF, enabled = viewModel.bluetoothConnectionState != BluetoothService.STATE_OFF,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@ -121,6 +121,13 @@ fun SettingsView(
stringResource(id = R.string.connect_button_label) 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 -> pairedDevices.value.forEach { device ->
if (device == null) return if (device == null) return
ListItem( ListItem(headlineContent = { Text(device.getName()) }, supportingContent = {
headlineContent = { Text(device.getName()) },
supportingContent = {
if (device.getName() != device.getAddress()) Text( if (device.getName() != device.getAddress()) Text(
device.getAddress() device.getAddress()
) )
}, }, modifier = Modifier.clickable(onClick = {
modifier = Modifier.clickable(onClick = {
viewModel.connectBluetoothDevice( viewModel.connectBluetoothDevice(
device device
) )
}), }), trailingContent = {
trailingContent = { if (device == viewModel.currentBluetoothDevice) if (viewModel.bluetoothConnectionState == BluetoothService.STATE_CONNECTED) Icon(
if (device == viewModel.currentBluetoothDevice)
if (viewModel.bluetoothConnectionState == BluetoothService.STATE_CONNECTED)
Icon(
painterResource(id = R.drawable.baseline_check_24), painterResource(id = R.drawable.baseline_check_24),
contentDescription = "state" contentDescription = "state"
) )
else else CircularProgressIndicator(
CircularProgressIndicator(
modifier = Modifier.width(16.dp), modifier = Modifier.width(16.dp),
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant, trackColor = MaterialTheme.colorScheme.surfaceVariant,
) )
} })
)
} }
} }
} }

View File

@ -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.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.tvcontroller.Settings
import com.example.tvcontroller.data.BluetoothDevice import com.example.tvcontroller.data.BluetoothDevice
import com.example.tvcontroller.services.BluetoothService import com.example.tvcontroller.services.BluetoothService
import com.example.tvcontroller.services.DeviceService import com.example.tvcontroller.services.DeviceService
import com.example.tvcontroller.webrtc.RtcPeerConnection
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SettingsViewModel( class SettingsViewModel(
private val deviceService: DeviceService, private val deviceService: DeviceService,
private val bluetoothService: BluetoothService private val bluetoothService: BluetoothService,
private val rtcPeerConnection: RtcPeerConnection
) : ViewModel() { ) : ViewModel() {
var serverAddress by mutableStateOf(deviceService.getServerAddress()) var serverAddress by mutableStateOf(deviceService.getServerAddress())
private set private set
var deviceName by mutableStateOf(android.os.Build.MANUFACTURER + " " + android.os.Build.MODEL) var deviceName by mutableStateOf(Build.MANUFACTURER + " " + Build.MODEL)
private set private set
var registrationCode by mutableStateOf("") var registrationCode by mutableStateOf("")
private set private set
@ -46,15 +51,22 @@ class SettingsViewModel(
//Log.i("SettingsScreen", "Save settings: $serverUrl, $deviceName, $registrationCode") //Log.i("SettingsScreen", "Save settings: $serverUrl, $deviceName, $registrationCode")
viewModelScope.launch { viewModelScope.launch {
deviceService.setServerAddress(serverAddress) deviceService.setServerAddress(serverAddress)
deviceService.createIntegration(deviceName, registrationCode) deviceService.registerIntegration(deviceName, registrationCode)
updateConnectionState() updateConnectionState()
updateDeviceInfo()
}
}
fun connectRtcPeerConnection() {
viewModelScope.launch(Dispatchers.IO) {
rtcPeerConnection.connect()
} }
} }
private fun updateConnectionState() { private fun updateConnectionState() {
Log.i("SettingsViewModel", "Device token: ${deviceService.getToken()}")
connectionState = if (deviceService.getToken().isEmpty()) { connectionState = if (deviceService.getToken().isEmpty()) {
Settings.ConnectionState.Unregistered Settings.ConnectionState.Unregistered
return
} else { } else {
Settings.ConnectionState.Registered Settings.ConnectionState.Registered
} }
@ -100,11 +112,12 @@ class SettingsViewModel(
fun provideFactory( fun provideFactory(
deviceService: DeviceService, deviceService: DeviceService,
bluetoothService: BluetoothService bluetoothService: BluetoothService,
rtcPeerConnection: RtcPeerConnection
) = object : ViewModelProvider.Factory { ) = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SettingsViewModel(deviceService, bluetoothService) as T return SettingsViewModel(deviceService, bluetoothService, rtcPeerConnection) as T
} }
} }
} }

View File

@ -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
1 functionname protocol device subdevice function
2 INPUT SOURCE NECx2 7 7 1
3 POWER NECx2 7 7 2
4 1 NECx2 7 7 4
5 2 NECx2 7 7 5
6 3 NECx2 7 7 6
7 VOLUME + NECx2 7 7 7
8 4 NECx2 7 7 8
9 5 NECx2 7 7 9
10 6 NECx2 7 7 10
11 VOLUME - NECx2 7 7 11
12 7 NECx2 7 7 12
13 8 NECx2 7 7 13
14 9 NECx2 7 7 14
15 MUTE NECx2 7 7 15
16 CHANNEL - NECx2 7 7 16
17 0 NECx2 7 7 17
18 CHANNEL + NECx2 7 7 18
19 LAST NECx2 7 7 19
20 MENU NECx2 7 7 26
21 INFO NECx2 7 7 31
22 AD/SUBT NECx2 7 7 37
23 EXIT NECx2 7 7 45
24 E-MANUAL NECx2 7 7 63
25 TOOLS NECx2 7 7 75
26 GUIDE NECx2 7 7 79
27 RETURN NECx2 7 7 88
28 CURSOR UP NECx2 7 7 96
29 CURSOR DOWN NECx2 7 7 97
30 CURSOR RIGHT NECx2 7 7 98
31 CURSOR LEFT NECx2 7 7 101
32 ENTER NECx2 7 7 104
33 CH LIST NECx2 7 7 107
34 SMART HUB NECx2 7 7 121
35 3D NECx2 7 7 159
36 HDMI2 NECx2 7 7 190
37 HDMI3 NECx2 7 7 194
38 HDMI4 NECx2 7 7 197
39 HDMI1 NECx2 7 7 233