feat: scan for bluetooth devices, list in connect view

This commit is contained in:
Fritz Heiden 2025-03-25 18:54:32 +01:00
parent e3623cb128
commit 8eef70f59b
8 changed files with 360 additions and 111 deletions

View File

@ -5,8 +5,10 @@
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="false" /> android:required="false" />
<uses-feature android:name="android.hardware.bluetooth" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<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" /> <uses-permission android:name="android.permission.INTERNET" />

View File

@ -30,6 +30,7 @@ import androidx.compose.ui.res.painterResource
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import com.example.tvcontroller.services.BluetoothService import com.example.tvcontroller.services.BluetoothService
import com.example.tvcontroller.services.CameraService import com.example.tvcontroller.services.CameraService
import com.example.tvcontroller.services.ControllerService
import com.example.tvcontroller.services.DeviceService import com.example.tvcontroller.services.DeviceService
import com.example.tvcontroller.ui.views.CameraView import com.example.tvcontroller.ui.views.CameraView
import com.example.tvcontroller.ui.views.SettingsView import com.example.tvcontroller.ui.views.SettingsView
@ -39,19 +40,22 @@ class MainActivity : ComponentActivity() {
private lateinit var bluetoothService: BluetoothService private lateinit var bluetoothService: BluetoothService
private lateinit var deviceService: DeviceService private lateinit var deviceService: DeviceService
private lateinit var cameraService: CameraService private lateinit var cameraService: CameraService
private lateinit var controllerService: ControllerService
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
bluetoothService = BluetoothService(this.applicationContext) bluetoothService = BluetoothService(this.applicationContext)
deviceService = DeviceService(this.applicationContext) deviceService = DeviceService(this.applicationContext)
cameraService = CameraService(this.applicationContext) cameraService = CameraService(this.applicationContext)
controllerService = ControllerService(bluetoothService)
checkPermissions() checkPermissions()
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
TVControllerTheme { TVControllerTheme {
TvControllerApp( TvControllerApp(
bluetoothService = bluetoothService, deviceService = deviceService,
deviceService = deviceService controllerService = controllerService,
bluetoothService = bluetoothService
) )
} }
} }
@ -61,14 +65,18 @@ class MainActivity : ComponentActivity() {
if (!cameraService.hasRequiredPermissions()) { if (!cameraService.hasRequiredPermissions()) {
ActivityCompat.requestPermissions(this, CameraService.CAMERAX_PERMISSIONS, 0) ActivityCompat.requestPermissions(this, CameraService.CAMERAX_PERMISSIONS, 0)
} }
if (!bluetoothService.hasRequiredPermissions()) {
ActivityCompat.requestPermissions(this, BluetoothService.BLUETOOTH_PERMISSIONS, 0)
}
} }
} }
@Composable @Composable
fun TvControllerApp( fun TvControllerApp(
navController: NavHostController = rememberNavController(), navController: NavHostController = rememberNavController(),
bluetoothService: BluetoothService, deviceService: DeviceService,
deviceService: DeviceService controllerService: ControllerService,
bluetoothService: BluetoothService
) { ) {
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)
@ -112,24 +120,23 @@ fun TvControllerApp(
) )
} }
}) { innerPadding -> }) { innerPadding ->
Column { NavHost(
NavHost( navController = navController,
navController = navController, startDestination = Screen.Camera.name,
startDestination = Screen.Camera.name, modifier = Modifier.padding(innerPadding)
modifier = Modifier.padding(innerPadding) ) {
) { composable(route = Screen.Camera.name) {
composable(route = Screen.Camera.name) { CameraView()
CameraView() }
} composable(route = Screen.Remote.name) {
composable(route = Screen.Remote.name) { RemoteScreen()
RemoteScreen() }
} composable(route = Screen.Settings.name) {
composable(route = Screen.Settings.name) { SettingsView(
SettingsView( deviceService = deviceService,
deviceService = deviceService, controllerService = controllerService,
bluetoothService = bluetoothService bluetoothService = bluetoothService
) )
}
} }
} }
} }

View File

@ -1,18 +1,22 @@
package com.example.tvcontroller package com.example.tvcontroller
import android.bluetooth.BluetoothAdapter import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.services.BluetoothService import com.example.tvcontroller.services.BluetoothService
import com.example.tvcontroller.services.ControllerService
import com.example.tvcontroller.services.DeviceService import com.example.tvcontroller.services.DeviceService
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SettingsViewModel( class SettingsViewModel(
private val deviceService: DeviceService, private val deviceService: DeviceService,
controllerService: ControllerService,
private val bluetoothService: BluetoothService private val bluetoothService: BluetoothService
) : ViewModel() { ) : ViewModel() {
var serverAddress by mutableStateOf(deviceService.getServerAddress()) var serverAddress by mutableStateOf(deviceService.getServerAddress())
@ -23,16 +27,20 @@ class SettingsViewModel(
private set private set
var connectionState by mutableStateOf(Settings.ConnectionState.Unregistered) var connectionState by mutableStateOf(Settings.ConnectionState.Unregistered)
private set private set
var bluetoothEnabled by mutableStateOf(bluetoothService.isBluetoothEnabled()) var controllerConnectionState by mutableIntStateOf(controllerService.connectionState)
private set private set
var activeView by mutableStateOf(MAIN_SETTINGS_VIEW)
var scannedDevices = bluetoothService.scannedDevices
var pairedDevices = bluetoothService.pairedDevices
init { init {
updateConnectionState() updateConnectionState()
viewModelScope.launch { viewModelScope.launch {
updateDeviceInfo() updateDeviceInfo()
} }
bluetoothService.onBluetoothStateChanged { controllerService.onConnectionStateChanged {
bluetoothEnabled = it == BluetoothAdapter.STATE_ON controllerConnectionState = it
} }
} }
@ -45,6 +53,11 @@ class SettingsViewModel(
} }
} }
fun connectController() {
//Log.i("SettingsScreen", "Connect controller")
//controllerService.connect()
}
private fun updateConnectionState() { private fun updateConnectionState() {
connectionState = if (deviceService.getToken().isEmpty()) { connectionState = if (deviceService.getToken().isEmpty()) {
Settings.ConnectionState.Unregistered Settings.ConnectionState.Unregistered
@ -76,14 +89,26 @@ class SettingsViewModel(
registrationCode = code registrationCode = code
} }
fun startBluetoothScan() {
bluetoothService.startDiscovery()
}
fun stopBluetoothScan() {
bluetoothService.stopDiscovery()
}
companion object { companion object {
const val MAIN_SETTINGS_VIEW = 0
const val CONNECT_CONTROLLER_VIEW = 1
fun provideFactory( fun provideFactory(
deviceService: DeviceService, deviceService: DeviceService,
bluetoothService: BluetoothService, controllerService: ControllerService,
bluetoothService: BluetoothService
) = 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, controllerService, bluetoothService) as T
} }
} }
} }

View File

@ -0,0 +1,27 @@
package com.example.tvcontroller.data
import android.util.Log
class BluetoothDevice(name: String?, address: String?) {
private var _name: String? = name
private var _address: String? = address
fun getName(): String {
return if (_name.isNullOrEmpty()) getAddress() else _name!!
}
fun getAddress(): String {
return if (_address == null) "" else _address!!
}
companion object {
fun fromBluetoothDevice(device: android.bluetooth.BluetoothDevice): BluetoothDevice {
try {
return BluetoothDevice(device.name, device.address)
} catch (e: SecurityException) {
Log.e("BluetoothDevice", "Error creating BluetoothDevice", e)
}
return BluetoothDevice(null, null)
}
}
}

View File

@ -6,17 +6,53 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import com.example.tvcontroller.data.BluetoothDevice
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class BluetoothService(private val context: Context) { class BluetoothService(private val context: Context) {
private var bluetoothManager: BluetoothManager = private var bluetoothManager: BluetoothManager =
getSystemService(context, BluetoothManager::class.java)!!; getSystemService(context, BluetoothManager::class.java)!!;
private var bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter private var bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
private var bluetoothStateReceiver: BroadcastReceiver? = null
private var bluetoothStateChangedCallbacks: MutableList<(Int) -> Unit> = mutableListOf() private var bluetoothStateChangedCallbacks: MutableList<(Int) -> Unit> = mutableListOf()
private val _scannedDevices = MutableStateFlow<List<BluetoothDevice>>(emptyList())
var scannedDevices: StateFlow<List<BluetoothDevice>> = _scannedDevices.asStateFlow()
private val _pairedDevices = MutableStateFlow<List<BluetoothDevice>>(emptyList())
var pairedDevices: StateFlow<List<BluetoothDevice>> = _pairedDevices.asStateFlow()
private var bluetoothStateReceiver: BroadcastReceiver? = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
bluetoothStateChangedCallbacks.forEach { it(state) }
}
}
private var foundDeviceReceiver: BroadcastReceiver? = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(
android.bluetooth.BluetoothDevice.EXTRA_DEVICE,
android.bluetooth.BluetoothDevice::class.java
)
} else {
intent.getParcelableExtra(android.bluetooth.BluetoothDevice.EXTRA_DEVICE)
}
if (device != null) {
val newDevice = BluetoothDevice.fromBluetoothDevice(device)
Log.i("BluetoothService", "Found device: $newDevice")
_scannedDevices.update { devices -> if (newDevice in devices) devices else devices + newDevice }
}
}
}
init { init {
registerBluetoothStateReceiver() registerBluetoothStateReceiver()
updatePairedDevices()
} }
fun isBluetoothEnabled(): Boolean { fun isBluetoothEnabled(): Boolean {
@ -24,32 +60,9 @@ class BluetoothService(private val context: Context) {
} }
private fun registerBluetoothStateReceiver() { private fun registerBluetoothStateReceiver() {
bluetoothStateReceiver = object : BroadcastReceiver() { context.registerReceiver(
override fun onReceive(context: Context, intent: Intent) { bluetoothStateReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) )
bluetoothStateChangedCallbacks.forEach { it(state) }
when (state) {
BluetoothAdapter.STATE_ON -> {
// Handle Bluetooth turned on
println("SIGNAL: Bluetooth is enabled")
}
BluetoothAdapter.STATE_OFF -> {
// Handle Bluetooth turned off
println("SIGNAL: Bluetooth is disabled")
}
BluetoothAdapter.STATE_TURNING_ON -> {
// Handle Bluetooth turning on
println("SIGNAL: Bluetooth is turning on")
}
BluetoothAdapter.STATE_TURNING_OFF -> {
// Handle Bluetooth turning off
println("SIGNAL: Bluetooth is turning off")
}
}
}
}
context.registerReceiver(bluetoothStateReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
} }
fun onBluetoothStateChanged(callback: (Int) -> Unit) { fun onBluetoothStateChanged(callback: (Int) -> Unit) {
@ -62,5 +75,69 @@ class BluetoothService(private val context: Context) {
fun cleanUp() { fun cleanUp() {
context.unregisterReceiver(bluetoothStateReceiver) context.unregisterReceiver(bluetoothStateReceiver)
context.unregisterReceiver(foundDeviceReceiver)
}
fun updatePairedDevices() {
try {
_pairedDevices.update {
bluetoothAdapter.bondedDevices.toList()
.map { device -> BluetoothDevice.fromBluetoothDevice(device) }
}
} catch (e: SecurityException) {
println("Error updating paired devices: $e")
}
}
fun startDiscovery() {
try {
if (bluetoothAdapter.isDiscovering) return
Log.i("BluetoothService", "Starting discovery")
context.registerReceiver(
foundDeviceReceiver,
IntentFilter(android.bluetooth.BluetoothDevice.ACTION_FOUND)
)
updatePairedDevices()
bluetoothAdapter.startDiscovery()
} catch (e: SecurityException) {
println("Error starting discovery: $e")
Log.e("BluetoothService", "Error starting discovery: $e")
}
}
fun stopDiscovery() {
context.unregisterReceiver(foundDeviceReceiver)
try {
bluetoothAdapter.cancelDiscovery()
} catch (e: SecurityException) {
println("Error stopping discovery: $e")
}
}
fun hasRequiredPermissions(): Boolean {
return BLUETOOTH_PERMISSIONS.all {
context.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED
}
}
fun getBluetoothState(): Int {
return bluetoothAdapter.state
}
companion object {
val BLUETOOTH_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(
android.Manifest.permission.BLUETOOTH,
android.Manifest.permission.BLUETOOTH_CONNECT,
android.Manifest.permission.BLUETOOTH_SCAN,
android.Manifest.permission.BLUETOOTH_ADMIN
)
} else {
arrayOf(
android.Manifest.permission.BLUETOOTH,
android.Manifest.permission.BLUETOOTH_ADMIN
)
}
} }
} }

View File

@ -0,0 +1,40 @@
package com.example.tvcontroller.services
import android.bluetooth.BluetoothAdapter
class ControllerService(bluetoothService: BluetoothService) {
private var connectionStateChangedListener: MutableList<(Int) -> Unit> = mutableListOf()
var connectionState = BLUETOOTH_DISABLED
private set
init {
handleBluetoothStateChanged(bluetoothService.getBluetoothState())
bluetoothService.onBluetoothStateChanged {
handleBluetoothStateChanged(it)
}
}
fun onConnectionStateChanged(callback: (Int) -> Unit) {
connectionStateChangedListener.add(callback)
}
private fun setConnectionState(state: Int) {
connectionState = state
connectionStateChangedListener.forEach { it(state) }
}
private fun handleBluetoothStateChanged(state: Int) {
when (state) {
BluetoothAdapter.STATE_OFF -> setConnectionState(BLUETOOTH_DISABLED)
else -> setConnectionState(NOT_PAIRED)
}
}
companion object {
const val BLUETOOTH_DISABLED = 0
const val NOT_PAIRED = 1
const val NOT_CONNECTED = 2
const val CONNECTED = 3
}
}

View File

@ -1,10 +1,15 @@
package com.example.tvcontroller.ui.views package com.example.tvcontroller.ui.views
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
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
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -12,6 +17,7 @@ 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.collectAsState
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
@ -20,77 +26,124 @@ import com.example.tvcontroller.R
import com.example.tvcontroller.Settings import com.example.tvcontroller.Settings
import com.example.tvcontroller.SettingsViewModel import com.example.tvcontroller.SettingsViewModel
import com.example.tvcontroller.services.BluetoothService import com.example.tvcontroller.services.BluetoothService
import com.example.tvcontroller.services.ControllerService
import com.example.tvcontroller.services.DeviceService import com.example.tvcontroller.services.DeviceService
import kotlinx.coroutines.flow.toList
@Composable @Composable
fun SettingsView( fun SettingsView(
deviceService: DeviceService, deviceService: DeviceService,
controllerService: ControllerService,
bluetoothService: BluetoothService bluetoothService: BluetoothService
) { ) {
val viewModel = val viewModel = viewModel<SettingsViewModel>(
viewModel<SettingsViewModel>(factory = SettingsViewModel.provideFactory(deviceService, bluetoothService)) factory = SettingsViewModel.provideFactory(
deviceService, controllerService, bluetoothService
)
)
Column( @Composable
modifier = Modifier fun MainSettingsView() {
.padding(16.dp, 16.dp) Column(
.verticalScroll(rememberScrollState()), modifier = Modifier
verticalArrangement = Arrangement.spacedBy(8.dp) .padding(16.dp, 16.dp)
) { .verticalScroll(rememberScrollState()),
Text( verticalArrangement = Arrangement.spacedBy(8.dp)
text = stringResource(id = R.string.settings_title), ) {
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = stringResource(id = R.string.server_settings_heading),
style = MaterialTheme.typography.headlineSmall
)
Text(
text = stringResource(id = R.string.server_connection_label) + ": " + getConnectionStateString(
viewModel.connectionState
),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = viewModel.serverAddress,
onValueChange = viewModel::onServerAddressChanged,
label = { Text(stringResource(id = R.string.server_address_label)) }
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = viewModel.deviceName,
onValueChange = viewModel::onDeviceNameChanged,
label = { Text(stringResource(id = R.string.device_name_label)) }
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = viewModel.registrationCode,
onValueChange = viewModel::onRegistrationCodeChanged,
label = { Text(stringResource(id = R.string.registration_code_label)) }
)
OutlinedButton(onClick = { viewModel.connect() }, modifier = Modifier.fillMaxWidth()) {
Text( Text(
stringResource(id = R.string.connect_button_label) text = stringResource(id = R.string.settings_title),
style = MaterialTheme.typography.displaySmall
) )
} Spacer(modifier = Modifier.padding(8.dp))
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = stringResource(id = R.string.controller_settings_heading),
style = MaterialTheme.typography.headlineSmall
)
if (viewModel.bluetoothEnabled) {
Text( Text(
text = "Controller settings: Bluetooth is enabled.", text = stringResource(id = R.string.server_settings_heading),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.headlineSmall
) )
} else { Text(
Text( text = stringResource(id = R.string.server_connection_label) + ": " + getConnectionStateString(
text = "Bluetooth is disabled. Please enable it in settings.", viewModel.connectionState
), style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = viewModel.serverAddress,
onValueChange = viewModel::onServerAddressChanged,
label = { Text(stringResource(id = R.string.server_address_label)) })
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = viewModel.deviceName,
onValueChange = viewModel::onDeviceNameChanged,
label = { Text(stringResource(id = R.string.device_name_label)) })
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = viewModel.registrationCode,
onValueChange = viewModel::onRegistrationCodeChanged,
label = { Text(stringResource(id = R.string.registration_code_label)) })
OutlinedButton(onClick = { viewModel.connect() }, modifier = Modifier.fillMaxWidth()) {
Text(
stringResource(id = R.string.connect_button_label)
)
}
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = stringResource(id = R.string.controller_settings_heading),
style = MaterialTheme.typography.headlineSmall
)
Text(
text = "Controller status: " + getControllerConnectionStateString(viewModel.controllerConnectionState) + ".",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
OutlinedButton(
onClick = { viewModel.activeView = SettingsViewModel.CONNECT_CONTROLLER_VIEW },
enabled = viewModel.controllerConnectionState != ControllerService.BLUETOOTH_DISABLED && viewModel.controllerConnectionState != ControllerService.CONNECTED,
modifier = Modifier.fillMaxWidth()
) {
Text(
stringResource(id = R.string.connect_button_label)
)
}
} }
} }
@SuppressLint("MissingPermission")
@Composable
fun ConnectControllerView() {
viewModel.startBluetoothScan()
val pairedDevices = viewModel.pairedDevices.collectAsState()
val scannedDevices = viewModel.scannedDevices.collectAsState()
Column(
modifier = Modifier
.padding(16.dp, 16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(id = R.string.connect_controller_title),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = stringResource(id = R.string.paired_devices_label),
style = MaterialTheme.typography.headlineSmall
)
pairedDevices.value.forEach { device ->
Text(text = device.getName())
}
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = stringResource(id = R.string.scanned_devices_label),
style = MaterialTheme.typography.headlineSmall
)
scannedDevices.value.forEach { device ->
Text(text = device.getName())
}
}
}
when (viewModel.activeView) {
SettingsViewModel.MAIN_SETTINGS_VIEW -> MainSettingsView()
SettingsViewModel.CONNECT_CONTROLLER_VIEW -> ConnectControllerView()
}
} }
@Composable @Composable
@ -100,3 +153,14 @@ fun getConnectionStateString(state: Settings.ConnectionState): String {
Settings.ConnectionState.Registered -> stringResource(id = R.string.connection_state_registered) Settings.ConnectionState.Registered -> stringResource(id = R.string.connection_state_registered)
} }
} }
@Composable
fun getControllerConnectionStateString(state: Int): String {
return when (state) {
ControllerService.BLUETOOTH_DISABLED -> stringResource(id = R.string.controller_state_bluetooth_disabled)
ControllerService.NOT_PAIRED -> stringResource(id = R.string.controller_state_not_paired)
ControllerService.NOT_CONNECTED -> stringResource(id = R.string.controller_state_not_connected)
ControllerService.CONNECTED -> stringResource(id = R.string.controller_state_connected)
else -> "Unknown"
}
}

View File

@ -13,4 +13,11 @@
<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_unregistered">unregistered</string>
<string name="connection_state_registered">registered</string> <string name="connection_state_registered">registered</string>
<string name="controller_state_bluetooth_disabled">Bluetooth is turned off</string>
<string name="controller_state_not_paired">Not paired</string>
<string name="controller_state_not_connected">Not connected</string>
<string name="controller_state_connected">Connected</string>
<string name="paired_devices_label">Paired devices</string>
<string name="scanned_devices_label">Scanned devices</string>
<string name="connect_controller_title">Connect controller</string>
</resources> </resources>