feat: scan for bluetooth devices, list in connect view
This commit is contained in:
parent
e3623cb128
commit
8eef70f59b
@ -5,8 +5,10 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature android:name="android.hardware.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_CONNECT" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
@ -30,6 +30,7 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.example.tvcontroller.services.BluetoothService
|
||||
import com.example.tvcontroller.services.CameraService
|
||||
import com.example.tvcontroller.services.ControllerService
|
||||
import com.example.tvcontroller.services.DeviceService
|
||||
import com.example.tvcontroller.ui.views.CameraView
|
||||
import com.example.tvcontroller.ui.views.SettingsView
|
||||
@ -39,19 +40,22 @@ class MainActivity : ComponentActivity() {
|
||||
private lateinit var bluetoothService: BluetoothService
|
||||
private lateinit var deviceService: DeviceService
|
||||
private lateinit var cameraService: CameraService
|
||||
private lateinit var controllerService: ControllerService
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
bluetoothService = BluetoothService(this.applicationContext)
|
||||
deviceService = DeviceService(this.applicationContext)
|
||||
cameraService = CameraService(this.applicationContext)
|
||||
controllerService = ControllerService(bluetoothService)
|
||||
checkPermissions()
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
TVControllerTheme {
|
||||
TvControllerApp(
|
||||
bluetoothService = bluetoothService,
|
||||
deviceService = deviceService
|
||||
deviceService = deviceService,
|
||||
controllerService = controllerService,
|
||||
bluetoothService = bluetoothService
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -61,14 +65,18 @@ class MainActivity : ComponentActivity() {
|
||||
if (!cameraService.hasRequiredPermissions()) {
|
||||
ActivityCompat.requestPermissions(this, CameraService.CAMERAX_PERMISSIONS, 0)
|
||||
}
|
||||
if (!bluetoothService.hasRequiredPermissions()) {
|
||||
ActivityCompat.requestPermissions(this, BluetoothService.BLUETOOTH_PERMISSIONS, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvControllerApp(
|
||||
navController: NavHostController = rememberNavController(),
|
||||
bluetoothService: BluetoothService,
|
||||
deviceService: DeviceService
|
||||
deviceService: DeviceService,
|
||||
controllerService: ControllerService,
|
||||
bluetoothService: BluetoothService
|
||||
) {
|
||||
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentScreen = Screen.valueOf(backStackEntry?.destination?.route ?: Screen.Camera.name)
|
||||
@ -112,24 +120,23 @@ fun TvControllerApp(
|
||||
)
|
||||
}
|
||||
}) { innerPadding ->
|
||||
Column {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Camera.name,
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
composable(route = Screen.Camera.name) {
|
||||
CameraView()
|
||||
}
|
||||
composable(route = Screen.Remote.name) {
|
||||
RemoteScreen()
|
||||
}
|
||||
composable(route = Screen.Settings.name) {
|
||||
SettingsView(
|
||||
deviceService = deviceService,
|
||||
bluetoothService = bluetoothService
|
||||
)
|
||||
}
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Camera.name,
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
composable(route = Screen.Camera.name) {
|
||||
CameraView()
|
||||
}
|
||||
composable(route = Screen.Remote.name) {
|
||||
RemoteScreen()
|
||||
}
|
||||
composable(route = Screen.Settings.name) {
|
||||
SettingsView(
|
||||
deviceService = deviceService,
|
||||
controllerService = controllerService,
|
||||
bluetoothService = bluetoothService
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
package com.example.tvcontroller
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
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.services.BluetoothService
|
||||
import com.example.tvcontroller.services.ControllerService
|
||||
import com.example.tvcontroller.services.DeviceService
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SettingsViewModel(
|
||||
private val deviceService: DeviceService,
|
||||
controllerService: ControllerService,
|
||||
private val bluetoothService: BluetoothService
|
||||
) : ViewModel() {
|
||||
var serverAddress by mutableStateOf(deviceService.getServerAddress())
|
||||
@ -23,16 +27,20 @@ class SettingsViewModel(
|
||||
private set
|
||||
var connectionState by mutableStateOf(Settings.ConnectionState.Unregistered)
|
||||
private set
|
||||
var bluetoothEnabled by mutableStateOf(bluetoothService.isBluetoothEnabled())
|
||||
var controllerConnectionState by mutableIntStateOf(controllerService.connectionState)
|
||||
private set
|
||||
var activeView by mutableStateOf(MAIN_SETTINGS_VIEW)
|
||||
|
||||
var scannedDevices = bluetoothService.scannedDevices
|
||||
var pairedDevices = bluetoothService.pairedDevices
|
||||
|
||||
init {
|
||||
updateConnectionState()
|
||||
viewModelScope.launch {
|
||||
updateDeviceInfo()
|
||||
}
|
||||
bluetoothService.onBluetoothStateChanged {
|
||||
bluetoothEnabled = it == BluetoothAdapter.STATE_ON
|
||||
controllerService.onConnectionStateChanged {
|
||||
controllerConnectionState = it
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +53,11 @@ class SettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun connectController() {
|
||||
//Log.i("SettingsScreen", "Connect controller")
|
||||
//controllerService.connect()
|
||||
}
|
||||
|
||||
private fun updateConnectionState() {
|
||||
connectionState = if (deviceService.getToken().isEmpty()) {
|
||||
Settings.ConnectionState.Unregistered
|
||||
@ -76,14 +89,26 @@ class SettingsViewModel(
|
||||
registrationCode = code
|
||||
}
|
||||
|
||||
fun startBluetoothScan() {
|
||||
bluetoothService.startDiscovery()
|
||||
}
|
||||
|
||||
fun stopBluetoothScan() {
|
||||
bluetoothService.stopDiscovery()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAIN_SETTINGS_VIEW = 0
|
||||
const val CONNECT_CONTROLLER_VIEW = 1
|
||||
|
||||
fun provideFactory(
|
||||
deviceService: DeviceService,
|
||||
bluetoothService: BluetoothService,
|
||||
controllerService: ControllerService,
|
||||
bluetoothService: BluetoothService
|
||||
) = object : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return SettingsViewModel(deviceService, bluetoothService) as T
|
||||
return SettingsViewModel(deviceService, controllerService, bluetoothService) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,17 +6,53 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
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) {
|
||||
private var bluetoothManager: BluetoothManager =
|
||||
getSystemService(context, BluetoothManager::class.java)!!;
|
||||
private var bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
|
||||
private var bluetoothStateReceiver: BroadcastReceiver? = null
|
||||
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 {
|
||||
registerBluetoothStateReceiver()
|
||||
updatePairedDevices()
|
||||
}
|
||||
|
||||
fun isBluetoothEnabled(): Boolean {
|
||||
@ -24,32 +60,9 @@ class BluetoothService(private val context: Context) {
|
||||
}
|
||||
|
||||
private fun registerBluetoothStateReceiver() {
|
||||
bluetoothStateReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
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))
|
||||
context.registerReceiver(
|
||||
bluetoothStateReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
|
||||
)
|
||||
}
|
||||
|
||||
fun onBluetoothStateChanged(callback: (Int) -> Unit) {
|
||||
@ -62,5 +75,69 @@ class BluetoothService(private val context: Context) {
|
||||
|
||||
fun cleanUp() {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,15 @@
|
||||
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.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -12,6 +17,7 @@ import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -20,77 +26,124 @@ import com.example.tvcontroller.R
|
||||
import com.example.tvcontroller.Settings
|
||||
import com.example.tvcontroller.SettingsViewModel
|
||||
import com.example.tvcontroller.services.BluetoothService
|
||||
import com.example.tvcontroller.services.ControllerService
|
||||
import com.example.tvcontroller.services.DeviceService
|
||||
import kotlinx.coroutines.flow.toList
|
||||
|
||||
@Composable
|
||||
fun SettingsView(
|
||||
deviceService: DeviceService,
|
||||
controllerService: ControllerService,
|
||||
bluetoothService: BluetoothService
|
||||
) {
|
||||
val viewModel =
|
||||
viewModel<SettingsViewModel>(factory = SettingsViewModel.provideFactory(deviceService, bluetoothService))
|
||||
val viewModel = viewModel<SettingsViewModel>(
|
||||
factory = SettingsViewModel.provideFactory(
|
||||
deviceService, controllerService, bluetoothService
|
||||
)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp, 16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
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()) {
|
||||
@Composable
|
||||
fun MainSettingsView() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp, 16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
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))
|
||||
Text(
|
||||
text = stringResource(id = R.string.controller_settings_heading),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
if (viewModel.bluetoothEnabled) {
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
Text(
|
||||
text = "Controller settings: Bluetooth is enabled.",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "Bluetooth is disabled. Please enable it in settings.",
|
||||
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(
|
||||
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
|
||||
)
|
||||
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
|
||||
@ -100,3 +153,14 @@ fun getConnectionStateString(state: Settings.ConnectionState): String {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,4 +13,11 @@
|
||||
<string name="server_address_label">Server address</string>
|
||||
<string name="connection_state_unregistered">unregistered</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>
|
||||
Loading…
Reference in New Issue
Block a user