mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
fix(ble): implement scanning for unbonded devices in common connections ui (#4779)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
afe1356430
commit
5cc1e94a13
6 changed files with 83 additions and 10 deletions
|
|
@ -44,12 +44,14 @@ class AndroidScannerViewModel(
|
|||
getDiscoveredDevicesUseCase: GetDiscoveredDevicesUseCase,
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
private val usbRepository: UsbRepository,
|
||||
bleScanner: org.meshtastic.core.ble.BleScanner? = null,
|
||||
) : ScannerViewModel(
|
||||
serviceRepository,
|
||||
radioController,
|
||||
radioInterfaceService,
|
||||
recentAddressesDataSource,
|
||||
getDiscoveredDevicesUseCase,
|
||||
bleScanner,
|
||||
) {
|
||||
override fun requestBonding(entry: DeviceListEntry.Ble) {
|
||||
Logger.i { "Starting bonding for ${entry.device.address.anonymize}" }
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.datastore.RecentAddressesDataSource
|
||||
|
|
@ -48,21 +49,70 @@ open class ScannerViewModel(
|
|||
private val radioInterfaceService: RadioInterfaceService,
|
||||
private val recentAddressesDataSource: RecentAddressesDataSource,
|
||||
private val getDiscoveredDevicesUseCase: GetDiscoveredDevicesUseCase,
|
||||
private val bleScanner: org.meshtastic.core.ble.BleScanner? = null,
|
||||
) : ViewModel() {
|
||||
val showMockInterface: StateFlow<Boolean> = MutableStateFlow(radioInterfaceService.isMockInterface()).asStateFlow()
|
||||
|
||||
private val _errorText = MutableStateFlow<String?>(null)
|
||||
val errorText: StateFlow<String?> = _errorText.asStateFlow()
|
||||
|
||||
private val isBleScanningState = MutableStateFlow(false)
|
||||
val isBleScanning: StateFlow<Boolean> = isBleScanningState.asStateFlow()
|
||||
|
||||
private val scannedBleDevices = MutableStateFlow<Map<String, org.meshtastic.core.ble.BleDevice>>(emptyMap())
|
||||
|
||||
private var scanJob: kotlinx.coroutines.Job? = null
|
||||
|
||||
fun startBleScan() {
|
||||
if (isBleScanningState.value || bleScanner == null) return
|
||||
|
||||
isBleScanningState.value = true
|
||||
scannedBleDevices.value = emptyMap()
|
||||
|
||||
scanJob =
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
bleScanner
|
||||
.scan(
|
||||
timeout = kotlin.time.Duration.INFINITE,
|
||||
serviceUuid = org.meshtastic.core.ble.MeshtasticBleConstants.SERVICE_UUID,
|
||||
)
|
||||
.collect { device ->
|
||||
scannedBleDevices.update { current -> current + (device.address to device) }
|
||||
}
|
||||
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
|
||||
co.touchlab.kermit.Logger.w(e) { "BLE scan failed" }
|
||||
} finally {
|
||||
isBleScanningState.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopBleScan() {
|
||||
scanJob?.cancel()
|
||||
scanJob = null
|
||||
isBleScanningState.value = false
|
||||
}
|
||||
|
||||
private val discoveredDevicesFlow =
|
||||
showMockInterface
|
||||
.flatMapLatest { showMock -> getDiscoveredDevicesUseCase.invoke(showMock) }
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
|
||||
|
||||
/** A combined list of bonded BLE devices for the UI. */
|
||||
/** A combined list of bonded and scanned BLE devices for the UI. */
|
||||
val bleDevicesForUi: StateFlow<List<DeviceListEntry>> =
|
||||
discoveredDevicesFlow
|
||||
.map { it?.bleDevices ?: emptyList() }
|
||||
kotlinx.coroutines.flow
|
||||
.combine(discoveredDevicesFlow, scannedBleDevices) { discovered, scannedMap ->
|
||||
val bonded = discovered?.bleDevices?.filterIsInstance<DeviceListEntry.Ble>() ?: emptyList()
|
||||
val bondedAddresses = bonded.map { it.address }.toSet()
|
||||
|
||||
// Add scanned devices that aren't already in the bonded list
|
||||
val unbondedScanned =
|
||||
scannedMap.values.filter { it.address !in bondedAddresses }.map { DeviceListEntry.Ble(it) }
|
||||
|
||||
// Sort by name
|
||||
(bonded + unbondedScanned).sortedBy { it.name }
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
|
||||
/** UI StateFlow for USB devices. */
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -46,8 +48,14 @@ import org.meshtastic.feature.connections.ScannerViewModel
|
|||
@Composable
|
||||
fun BLEDevices(connectionState: ConnectionState, selectedDevice: String, scanModel: ScannerViewModel) {
|
||||
val bleDevices by scanModel.bleDevicesForUi.collectAsStateWithLifecycle()
|
||||
val isScanning by scanModel.isBleScanning.collectAsStateWithLifecycle()
|
||||
|
||||
Column {
|
||||
DisposableEffect(Unit) {
|
||||
scanModel.startBleScan()
|
||||
onDispose { scanModel.stopBleScan() }
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = stringResource(Res.string.bluetooth_available_devices),
|
||||
modifier = Modifier.padding(horizontal = 8.dp).padding(bottom = 16.dp).fillMaxWidth(),
|
||||
|
|
@ -55,6 +63,10 @@ fun BLEDevices(connectionState: ConnectionState, selectedDevice: String, scanMod
|
|||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
|
||||
if (isScanning) {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp))
|
||||
}
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) {
|
||||
items(bleDevices, key = { it.fullAddress }) { device ->
|
||||
Card(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue