Compare commits

..

No commits in common. "52099b2e09ac0a5cfeb85f0c837c6244f72c05b3" and "7a871f9fa4f3a7209770da83846d3b31b749750f" have entirely different histories.

6 changed files with 15 additions and 107 deletions

View File

@ -31,13 +31,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.tvcontroller.services.BluetoothService import com.example.tvcontroller.services.BluetoothService
import com.example.tvcontroller.services.DeviceService
import com.example.tvcontroller.ui.AppViewModel import com.example.tvcontroller.ui.AppViewModel
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var bluetoothService: BluetoothService private lateinit var bluetoothService: BluetoothService
private lateinit var deviceService: DeviceService
private val appViewModel by viewModels<AppViewModel>() private val appViewModel by viewModels<AppViewModel>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -45,15 +43,10 @@ class MainActivity : ComponentActivity() {
bluetoothService = BluetoothService(this.applicationContext) bluetoothService = BluetoothService(this.applicationContext)
bluetoothService.onBluetoothStateChanged { state -> appViewModel.setBluetoothEnabled(state == BluetoothAdapter.STATE_ON) } bluetoothService.onBluetoothStateChanged { state -> appViewModel.setBluetoothEnabled(state == BluetoothAdapter.STATE_ON) }
appViewModel.setBluetoothEnabled(bluetoothService.isBluetoothEnabled()) appViewModel.setBluetoothEnabled(bluetoothService.isBluetoothEnabled())
deviceService = DeviceService(this.applicationContext)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
TVControllerTheme { TVControllerTheme {
TvControllerApp( TvControllerApp(appViewModel = appViewModel, bluetoothService = bluetoothService)
appViewModel = appViewModel,
bluetoothService = bluetoothService,
deviceService = deviceService
)
} }
} }
} }
@ -63,8 +56,7 @@ class MainActivity : ComponentActivity() {
fun TvControllerApp( fun TvControllerApp(
navController: NavHostController = rememberNavController(), navController: NavHostController = rememberNavController(),
appViewModel: AppViewModel = viewModel(), appViewModel: AppViewModel = viewModel(),
bluetoothService: BluetoothService? = null, bluetoothService: BluetoothService? = null
deviceService: DeviceService
) { ) {
val backStackEntry by navController.currentBackStackEntryAsState() val backStackEntry by navController.currentBackStackEntryAsState()
val currentScreen = Screen.valueOf(backStackEntry?.destination?.route ?: Screen.Camera.name) val currentScreen = Screen.valueOf(backStackEntry?.destination?.route ?: Screen.Camera.name)
@ -121,7 +113,7 @@ fun TvControllerApp(
RemoteScreen() RemoteScreen()
} }
composable(route = Screen.Settings.name) { composable(route = Screen.Settings.name) {
SettingsScreen(appViewModel = appViewModel, deviceService = deviceService) SettingsScreen(appViewModel = appViewModel)
} }
} }
} }

View File

@ -1,8 +0,0 @@
package com.example.tvcontroller
object Settings {
enum class ConnectionState {
Unregistered,
Registered,
}
}

View File

@ -16,13 +16,11 @@ 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
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.tvcontroller.services.DeviceService
import com.example.tvcontroller.ui.AppViewModel import com.example.tvcontroller.ui.AppViewModel
@Composable @Composable
fun SettingsScreen(deviceService: DeviceService, appViewModel: AppViewModel) { fun SettingsScreen(appViewModel: AppViewModel = viewModel()) {
val viewModel = val viewModel = viewModel<SettingsViewModel>()
viewModel<SettingsViewModel>(factory = SettingsViewModel.provideFactory(deviceService))
Column( Column(
modifier = Modifier modifier = Modifier
@ -40,9 +38,7 @@ fun SettingsScreen(deviceService: DeviceService, appViewModel: AppViewModel) {
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall
) )
Text( Text(
text = stringResource(id = R.string.server_connection_label) + ": " + getConnectionStateString( text = stringResource(id = R.string.server_connection_label) + ": " + stringResource(id = R.string.connection_state_disconnected),
viewModel.connectionState
),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
OutlinedTextField( OutlinedTextField(
@ -86,11 +82,3 @@ fun SettingsScreen(deviceService: DeviceService, appViewModel: AppViewModel) {
} }
} }
} }
@Composable
fun getConnectionStateString(state: Settings.ConnectionState): String {
return when (state) {
Settings.ConnectionState.Unregistered -> stringResource(id = R.string.connection_state_unregistered)
Settings.ConnectionState.Registered -> stringResource(id = R.string.connection_state_registered)
}
}

View File

@ -4,40 +4,23 @@ 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.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.tvcontroller.services.DeviceService import com.example.tvcontroller.services.DeviceService
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SettingsViewModel(private val deviceService: DeviceService) : ViewModel() { class SettingsViewModel : 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(android.os.Build.MANUFACTURER + " " + android.os.Build.MODEL)
private set private set
var registrationCode by mutableStateOf("") var registrationCode by mutableStateOf("")
private set private set
var connectionState by mutableStateOf(Settings.ConnectionState.Unregistered)
private set
init {
checkConnectionState()
}
fun connect() { fun connect() {
//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.createIntegration(deviceName, registrationCode)
checkConnectionState()
}
}
private fun checkConnectionState() {
connectionState = if (deviceService.getToken().isEmpty()) {
Settings.ConnectionState.Unregistered
return
} else {
Settings.ConnectionState.Registered
} }
} }
@ -52,15 +35,4 @@ class SettingsViewModel(private val deviceService: DeviceService) : ViewModel()
fun onRegistrationCodeChanged(code: String) { fun onRegistrationCodeChanged(code: String) {
registrationCode = code registrationCode = code
} }
companion object {
fun provideFactory(
deviceService: DeviceService,
) = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SettingsViewModel(deviceService) as T
}
}
}
} }

View File

@ -1,7 +1,5 @@
package com.example.tvcontroller.services package com.example.tvcontroller.services
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.util.Log import android.util.Log
import io.ktor.client.engine.cio.* import io.ktor.client.engine.cio.*
import io.ktor.client.* import io.ktor.client.*
@ -13,59 +11,31 @@ import io.ktor.client.statement.HttpResponse
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"; object DeviceService {
class DeviceService(private val context: Context) {
private var client = HttpClient(CIO) private var client = HttpClient(CIO)
private var serverAddress: String = "" private var serverAddress: String = ""
private var token: String = ""
init {
loadPreferences()
}
suspend fun createIntegration(name: String, code: String) { suspend fun createIntegration(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 json = JSONObject()
requestJson.put("name", name) json.put("name", name)
requestJson.put("code", code) json.put("code", code)
try { try {
val response: HttpResponse = client.request("http://$serverAddress/api/integrations") { val response: HttpResponse = client.request("http://$serverAddress/api/integrations") {
method = HttpMethod.Post method = HttpMethod.Post
setBody(requestJson.toString()) setBody(json.toString())
headers { headers {
append("Content-Type", "application/json") append("Content-Type", "application/json")
} }
} }
val body: String = response.body() val body: String = response.body()
val responseJson = JSONObject(body)
token = responseJson.getString("token")
savePreferences()
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 creating integration", e)
} }
} }
private fun loadPreferences() {
val sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE)
serverAddress = sharedPreferences.getString("server_address", "")!!
token = sharedPreferences.getString("token", "")!!
Log.i("DeviceService", "Loaded preferences: $serverAddress $token")
}
private fun savePreferences() {
val sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.apply {
putString("server_address", serverAddress)
putString("token", token)
apply()
}
}
fun setServerAddress(url: String) { fun setServerAddress(url: String) {
serverAddress = url serverAddress = url
} }
@ -73,8 +43,4 @@ class DeviceService(private val context: Context) {
fun getServerAddress(): String { fun getServerAddress(): String {
return serverAddress return serverAddress
} }
fun getToken(): String {
return token
}
} }

View File

@ -11,6 +11,4 @@
<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> <string name="server_address_label">Server address</string>
<string name="connection_state_unregistered">unregistered</string>
<string name="connection_state_registered">registered</string>
</resources> </resources>