Paired devices list was added

This commit is contained in:
2023-09-27 21:55:07 +07:00
parent 80390b09ba
commit 415b5ef0d8
11 changed files with 298 additions and 93 deletions

View File

@@ -9,29 +9,13 @@ import android.content.Intent
import android.location.LocationManager import android.location.LocationManager
import android.os.Build import android.os.Build
class BluetoothIntentReceiver( class BluetoothAdapterStateReceiver(
private val onDeviceFound: (device: BluetoothDevice, rssi: Short) -> Unit,
private val onBluetoothEnabledChanged: (isBluetoothEnabled: Boolean) -> Unit, private val onBluetoothEnabledChanged: (isBluetoothEnabled: Boolean) -> Unit,
private val onDiscoveryRunningChanged: (isDiscoveryRunning: Boolean) -> Unit, private val onDiscoveryRunningChanged: (isDiscoveryRunning: Boolean) -> Unit,
private val onLocationEnabledChanged: () -> Unit private val onLocationEnabledChanged: () -> Unit
) : BroadcastReceiver() { ) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) { when (intent?.action) {
BluetoothDevice.ACTION_FOUND -> {
val device = if (Build.VERSION.SDK_INT >= 33) {
intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE,
BluetoothDevice::class.java
)
} else {
@Suppress("DEPRECATION") intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
val rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE)
@SuppressLint("MissingPermission") if (device?.name != null)
onDeviceFound(device, rssi)
}
BluetoothAdapter.ACTION_STATE_CHANGED -> { BluetoothAdapter.ACTION_STATE_CHANGED -> {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) { when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
BluetoothAdapter.STATE_ON -> { BluetoothAdapter.STATE_ON -> {
@@ -57,3 +41,32 @@ class BluetoothIntentReceiver(
} }
} }
} }
class BluetoothStateReceiver(
private val onDeviceFound: (device: BluetoothDevice, rssi: Short) -> Unit,
private val onConnectedStateChanged: (isConnected: Boolean, BluetoothDevice) -> Unit
) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent?.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE,
BluetoothDevice::class.java
)
} else {
@Suppress("DEPRECATION") intent?.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
when(intent?.action) {
BluetoothDevice.ACTION_FOUND -> {
val rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE)
@SuppressLint("MissingPermission") if (device?.name != null)
onDeviceFound(device, rssi)
}
BluetoothDevice.ACTION_ACL_CONNECTED -> {
onConnectedStateChanged(true, device ?: return)
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
onConnectedStateChanged(false, device ?: return)
}
}
}
}

View File

@@ -14,6 +14,7 @@ import android.os.Build
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@@ -22,15 +23,20 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.toSet
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.io.IOException import java.io.IOException
import java.util.UUID import java.util.UUID
sealed interface ConnectionResult { sealed interface ConnectionResult {
object ConnectionEstablished: ConnectionResult object ConnectionEstablished: ConnectionResult
data class TransferSucceded(val message: KMessage): ConnectionResult
data class Error(val message: String) : ConnectionResult data class Error(val message: String) : ConnectionResult
} }
@@ -40,12 +46,13 @@ interface BluetoothController {
val isConnected: StateFlow<Boolean> val isConnected: StateFlow<Boolean>
val isScanning: StateFlow<Boolean> val isScanning: StateFlow<Boolean>
val scannedDevices: StateFlow<List<Device>> val scannedDevices: StateFlow<List<Device>>
val pairedDevices: StateFlow<Set<BluetoothDevice>> val pairedDevices: StateFlow<List<Device>>
val errors: SharedFlow<String> val errors: SharedFlow<String>
fun startDiscovery() fun startDiscovery()
fun cancelDiscovery() fun cancelDiscovery()
fun connectToDevice(device: Device?): Flow<ConnectionResult> fun connectToDevice(device: Device?): Flow<ConnectionResult>
suspend fun trySendMessage(message: KMessage): KMessage?
fun closeConnection() fun closeConnection()
fun onDestroy() fun onDestroy()
} }
@@ -64,6 +71,8 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
context.getSystemService(ComponentActivity.LOCATION_SERVICE) as LocationManager context.getSystemService(ComponentActivity.LOCATION_SERVICE) as LocationManager
} }
private var dataTransferService: BluetoothDataTransferService? = null
private val _isConnected: MutableStateFlow<Boolean> = MutableStateFlow(false) private val _isConnected: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isConnected: StateFlow<Boolean> override val isConnected: StateFlow<Boolean>
get() = _isConnected.asStateFlow() get() = _isConnected.asStateFlow()
@@ -84,8 +93,8 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
override val isLocationEnabled: StateFlow<Boolean> override val isLocationEnabled: StateFlow<Boolean>
get() = _isLocationEnabled.asStateFlow() get() = _isLocationEnabled.asStateFlow()
private val _pairedDevices = MutableStateFlow<Set<BluetoothDevice>>(emptySet()) private val _pairedDevices = MutableStateFlow<List<Device>>(emptyList())
override val pairedDevices: StateFlow<Set<BluetoothDevice>> override val pairedDevices: StateFlow<List<Device>>
get() = _pairedDevices.asStateFlow() get() = _pairedDevices.asStateFlow()
private val _scannedDevices: MutableStateFlow<List<Device>> = MutableStateFlow(emptyList()) private val _scannedDevices: MutableStateFlow<List<Device>> = MutableStateFlow(emptyList())
@@ -94,20 +103,8 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
private var currentClientSocket: BluetoothSocket? = null private var currentClientSocket: BluetoothSocket? = null
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private val bluetoothIntentReceiver = BluetoothIntentReceiver( private val bluetoothAdapterStateReceiver = BluetoothAdapterStateReceiver(
onDeviceFound = {device, rssi ->
if(!hasAllPermissions()) return@BluetoothIntentReceiver
val newDevice = Device(device, rssi)
_scannedDevices.update { devices ->
if(newDevice in devices) devices else devices + newDevice
}
Log.i(
"ScanActivity",
"Found new device: ${device.name} ${device.address} $rssi"
)
},
onBluetoothEnabledChanged = { isEnabled -> onBluetoothEnabledChanged = { isEnabled ->
_isEnabled.update { _ -> isEnabled } _isEnabled.update { _ -> isEnabled }
startDiscovery() startDiscovery()
@@ -125,6 +122,30 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
} }
) )
@SuppressLint("MissingPermission")
private val bluetoothStateReceiver = BluetoothStateReceiver(
onDeviceFound = { device, rssi ->
if(!hasAllPermissions()) return@BluetoothStateReceiver
val newDevice = Device(device, rssi)
_scannedDevices.update { devices ->
if(newDevice in devices) devices else devices + newDevice
}
Log.i(
"ScanActivity",
"Found new device: ${device.name} ${device.address} $rssi"
)
},
onConnectedStateChanged = { isConnected, device ->
if(bluetoothAdapter?.bondedDevices?.contains(device) == true) {
_isConnected.update { isConnected }
} else {
CoroutineScope(Dispatchers.IO).launch {
_errors.emit("Can't connect to a non-paired device.")
}
}
}
)
companion object { companion object {
const val SERVICE_UUID = "af7cc14b-cffa-4a3d-b677-01b0ff0a93d7" const val SERVICE_UUID = "af7cc14b-cffa-4a3d-b677-01b0ff0a93d7"
} }
@@ -133,14 +154,27 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
updatePairedDevices() updatePairedDevices()
_isEnabled.update { bluetoothAdapter.isEnabled } _isEnabled.update { bluetoothAdapter.isEnabled }
_isLocationEnabled.update { locationManager?.isLocationEnabled == true } _isLocationEnabled.update { locationManager?.isLocationEnabled == true }
context.registerReceiver(bluetoothIntentReceiver, IntentFilter(BluetoothDevice.ACTION_FOUND)) context.registerReceiver(
context.registerReceiver(bluetoothIntentReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) bluetoothAdapterStateReceiver,
context.registerReceiver(bluetoothIntentReceiver, IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) IntentFilter().apply {
context.registerReceiver(bluetoothIntentReceiver, IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
context.registerReceiver(bluetoothIntentReceiver, IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)) addAction(LocationManager.PROVIDERS_CHANGED_ACTION)
} }
} }
)
context.registerReceiver(
bluetoothStateReceiver,
IntentFilter().apply {
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
addAction(BluetoothDevice.ACTION_FOUND)
}
)
}
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
override fun startDiscovery() { override fun startDiscovery() {
@@ -185,6 +219,13 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
try { try {
socket.connect() socket.connect()
emit(ConnectionResult.ConnectionEstablished) emit(ConnectionResult.ConnectionEstablished)
BluetoothDataTransferService(socket).also { it ->
dataTransferService = it
emitAll(
it.listenForIncomingMessages()
.map {ConnectionResult.TransferSucceded(it)}
)
}
} catch (e: IOException) { } catch (e: IOException) {
closeConnection() closeConnection()
emit(ConnectionResult.Error("Connection was interrupted")) emit(ConnectionResult.Error("Connection was interrupted"))
@@ -193,22 +234,40 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
}.onCompletion { closeConnection() }.flowOn(Dispatchers.IO) }.onCompletion { closeConnection() }.flowOn(Dispatchers.IO)
} }
override suspend fun trySendMessage(message: KMessage): KMessage? {
if(!hasAllPermissions()){
return null
}
if(dataTransferService == null) {
return null
}
dataTransferService?.sendMessage(message.toByteArray())
return message
}
override fun closeConnection() { override fun closeConnection() {
currentClientSocket?.close() currentClientSocket?.close()
currentClientSocket = null currentClientSocket = null
} }
override fun onDestroy() { override fun onDestroy() {
context.unregisterReceiver(bluetoothIntentReceiver) context.unregisterReceiver(bluetoothAdapterStateReceiver)
context.unregisterReceiver(bluetoothStateReceiver)
closeConnection() closeConnection()
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private fun updatePairedDevices() { private fun updatePairedDevices() {
if(!hasAllPermissions()) return if(!hasAllPermissions()) return
bluetoothAdapter?.bondedDevices.also { devices -> Log.i("ScanActivity", "${bluetoothAdapter?.bondedDevices}")
if(devices != null) { bluetoothAdapter?.bondedDevices?.onEach { device ->
_pairedDevices.update { devices } _pairedDevices.update {
val currentDevice = Device(bluetoothDevice = device, rssi=0, isPaired = true)
if (currentDevice in pairedDevices.value) {
pairedDevices.value
} else {
_pairedDevices.value + currentDevice
}
} }
} }
} }

View File

@@ -0,0 +1,48 @@
package com.helible.pilot
import android.bluetooth.BluetoothSocket
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import java.io.IOException
class TransferFailedException : IOException("Reading incoming data failed")
class BluetoothDataTransferService(
private val socket: BluetoothSocket
) {
fun listenForIncomingMessages(): Flow<KMessage> {
return flow {
if(!socket.isConnected)
return@flow
val buffer = ByteArray(128)
while(true) {
val byteCount: Int = try {
socket.inputStream.read(buffer)
} catch (e: IOException) {
throw TransferFailedException()
}
emit(
buffer.decodeToString(
endIndex = byteCount
).toKMessage()
)
}
}.flowOn(Dispatchers.IO)
}
suspend fun sendMessage(bytes: ByteArray): Boolean {
return withContext(Dispatchers.IO) {
try {
socket.outputStream.write(bytes)
} catch (e: IOException) {
e.printStackTrace()
return@withContext false
}
true
}
}
}

View File

@@ -0,0 +1,13 @@
package com.helible.pilot
import android.bluetooth.BluetoothDevice
data class BluetoothUiState(
val isEnabled: Boolean = false,
val isLocationEnabled: Boolean = false,
val isDiscovering: Boolean = false,
val isConnected: Boolean = false,
val isConnecting: Boolean = false,
val errorMessage: String? = null,
val scannedDevices: List<Device> = emptyList(),
val pairedDevices: List<Device> = emptyList(),
)

View File

@@ -1,7 +1,6 @@
package com.helible.pilot package com.helible.pilot
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -16,18 +15,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
data class BluetoothUiState(
val isEnabled: Boolean = false,
val isLocationEnabled: Boolean = false,
val isDiscovering: Boolean = false,
val isConnected: Boolean = false,
val isConnecting: Boolean = false,
val errorMessage: String? = null,
val scannedDevices: List<Device> = emptyList(),
val pairedDevices: List<BluetoothDevice> = emptyList(),
)
class BluetoothViewModel( class BluetoothViewModel(
private val bluetoothController: BluetoothController private val bluetoothController: BluetoothController
) : ViewModel() { ) : ViewModel() {
@@ -41,7 +28,7 @@ class BluetoothViewModel(
{ scannedDevices, pairedDevices, state -> { scannedDevices, pairedDevices, state ->
state.copy( state.copy(
scannedDevices = scannedDevices.toList(), scannedDevices = scannedDevices.toList(),
pairedDevices = pairedDevices.toList() pairedDevices = pairedDevices
) )
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), _state.value) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), _state.value)
@@ -83,6 +70,9 @@ class BluetoothViewModel(
) )
} }
} }
is ConnectionResult.TransferSucceded -> {
TODO("Telemetry not implemented")
}
is ConnectionResult.Error -> { is ConnectionResult.Error -> {
_state.update { it.copy( _state.update { it.copy(
isConnected = false, isConnected = false,

View File

@@ -0,0 +1,23 @@
package com.helible.pilot
// Todo: add checksum
// Todo: add arguments names
data class KMessage(
val r1: UShort,
val r2: UShort,
val r3: UShort,
val emergStop: Boolean,
val alarm: Boolean
)
fun KMessage.toByteArray(): ByteArray {
return "$$r1;$r2;$r3;$emergStop;$alarm\r\n".encodeToByteArray()
}
fun String.toKMessage(): KMessage {
// TODO: implement
return KMessage(
0u, 0u, 0u, emergStop = false, alarm = false
)
}

View File

@@ -1,27 +1,15 @@
package com.helible.pilot package com.helible.pilot
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.helible.pilot.components.SavedPreferences import com.helible.pilot.components.SavedPreferences
import com.helible.pilot.components.SavedPreferencesCache import com.helible.pilot.components.SavedPreferencesCache
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
data class Device( data class Device(
val bluetoothDevice: BluetoothDevice, val bluetoothDevice: BluetoothDevice,
val rssi: Short, val rssi: Short,
val isPaired: Boolean = false
) )
class PermissionDialogViewModel: ViewModel() { class PermissionDialogViewModel: ViewModel() {

View File

@@ -6,7 +6,6 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.provider.Settings import android.provider.Settings
import android.util.Log
import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList

View File

@@ -45,7 +45,7 @@ fun BluetoothScannerScreen(
val (title, devicesList, controls) = createRefs() val (title, devicesList, controls) = createRefs()
Title( Title(
text = "Поиск устройств", text = "Устройства поблизости",
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 10.dp) .padding(vertical = 10.dp)
@@ -53,7 +53,7 @@ fun BluetoothScannerScreen(
) )
DiscoveredDevicesList( DiscoveredDevicesList(
devices = bluetoothState.scannedDevices, bluetoothState = bluetoothState,
selectedDevice = selectedDevice, selectedDevice = selectedDevice,
choiceDevice = choiceDevice, choiceDevice = choiceDevice,
modifier = Modifier modifier = Modifier

View File

@@ -29,21 +29,35 @@ import com.helible.pilot.R
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@Composable @Composable
fun DeviceItem(deviceInfo: Device, selectedDevice: Device?, choiceDevice: (device: Device?) -> Unit, modifier: Modifier) { fun DeviceItem(
deviceInfo: Device,
selectedDevice: Device?,
choiceDevice: (device: Device?) -> Unit,
modifier: Modifier,
) {
ElevatedCard( ElevatedCard(
modifier = modifier.clickable { modifier = modifier.clickable {
choiceDevice(deviceInfo) choiceDevice(deviceInfo)
}, },
colors = CardDefaults.elevatedCardColors(containerColor = if (deviceInfo.bluetoothDevice == selectedDevice?.bluetoothDevice) colors = CardDefaults.elevatedCardColors(
containerColor = if (deviceInfo.bluetoothDevice == selectedDevice?.bluetoothDevice)
MaterialTheme.colorScheme.secondaryContainer MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surface
) )
) { ) {
Row(modifier = Modifier.padding(8.dp)) { Row(modifier = Modifier.padding(8.dp)) {
Column(verticalArrangement = Arrangement.Center) { Column(verticalArrangement = Arrangement.Center) {
Text(text=deviceInfo.bluetoothDevice.name, fontWeight = FontWeight.Bold, softWrap = true) Text(
Text(text="MAC: ${deviceInfo.bluetoothDevice.address}", fontWeight = FontWeight.Thin) text = deviceInfo.bluetoothDevice.name,
fontWeight = FontWeight.Bold,
softWrap = true
)
Text(
text = "MAC: ${deviceInfo.bluetoothDevice.address}",
fontWeight = FontWeight.Thin
)
} }
if (!deviceInfo.isPaired) {
Box(contentAlignment = Alignment.CenterEnd, modifier = Modifier.fillMaxSize()) { Box(contentAlignment = Alignment.CenterEnd, modifier = Modifier.fillMaxSize()) {
Icon( Icon(
painterResource(id = getSignalIconForRssiValue(deviceInfo.rssi)), painterResource(id = getSignalIconForRssiValue(deviceInfo.rssi)),
@@ -57,6 +71,7 @@ fun DeviceItem(deviceInfo: Device, selectedDevice: Device?, choiceDevice: (devic
} }
} }
} }
}
fun getSignalIconForRssiValue(rssi: Short): Int { fun getSignalIconForRssiValue(rssi: Short): Int {
if (rssi >= -80) return R.drawable.signal_icon4 if (rssi >= -80) return R.drawable.signal_icon4

View File

@@ -1,18 +1,36 @@
package com.helible.pilot.components package com.helible.pilot.components
import androidx.compose.foundation.layout.fillMaxSize
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.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.helible.pilot.BluetoothUiState
import com.helible.pilot.Device import com.helible.pilot.Device
@Composable @Composable
fun DiscoveredDevicesList(devices: List<Device>, selectedDevice: Device?, choiceDevice: (device: Device?) -> Unit, modifier: Modifier = Modifier) { fun DiscoveredDevicesList(
bluetoothState: BluetoothUiState,
selectedDevice: Device?,
choiceDevice: (device: Device?) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier) { LazyColumn(modifier = modifier) {
items(devices) { device -> item {
Text(
text = "Ранее подключенные устройства",
textAlign = TextAlign.Left,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(10.dp)
)
}
items(bluetoothState.pairedDevices) { device ->
DeviceItem( DeviceItem(
deviceInfo = device, deviceInfo = device,
selectedDevice = selectedDevice, selectedDevice = selectedDevice,
@@ -24,5 +42,44 @@ fun DiscoveredDevicesList(devices: List<Device>, selectedDevice: Device?, choice
) )
) )
} }
if(bluetoothState.pairedDevices.isEmpty()){
item {
Text(
text = "Нет элементов для отображения",
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxSize()
)
}
}
item {
Text(
text = "Доступные устройства",
textAlign = TextAlign.Left,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(10.dp)
)
}
items(bluetoothState.scannedDevices) { device ->
DeviceItem(
deviceInfo = device,
selectedDevice = selectedDevice,
choiceDevice = choiceDevice,
modifier = Modifier
.fillMaxWidth()
.padding(
5.dp
)
)
}
if(bluetoothState.pairedDevices.isEmpty() && !bluetoothState.isDiscovering) {
item {
Text(
text = "Устройства поблизости не обнаружены.",
modifier = Modifier.fillMaxSize()
)
}
}
} }
} }