From 502e41733892c4f21545067c53cd84d930f0696c Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:18:09 -0400 Subject: [PATCH] `ConnectionsScreen` available BLE devices (#3298) --- .../mesh/ui/connections/ConnectionsScreen.kt | 76 ++----------------- .../ui/connections/components/BLEDevices.kt | 52 +++++++++---- core/strings/src/main/res/values/strings.xml | 3 +- 3 files changed, 48 insertions(+), 83 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt index 6010b5eff..a601a1443 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt @@ -25,23 +25,18 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.selection.selectable -import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Language import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -49,14 +44,12 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.ConfigProtos @@ -118,7 +111,8 @@ fun ConnectionsScreen( val regionUnset = config.lora.region == ConfigProtos.Config.LoRaConfig.RegionCode.UNSET val bluetoothRssi by connectionsViewModel.bluetoothRssi.collectAsStateWithLifecycle() - val bleDevices by scanModel.bleDevicesForUi.collectAsStateWithLifecycle() + val bondedBleDevices by scanModel.bleDevicesForUi.collectAsStateWithLifecycle() + val scannedBleDevices by scanModel.scanResult.observeAsState(emptyMap()) val discoveredTcpDevices by scanModel.discoveredTcpDevicesForUi.collectAsStateWithLifecycle() val recentTcpDevices by scanModel.recentTcpDevicesForUi.collectAsStateWithLifecycle() val usbDevices by scanModel.usbDevicesForUi.collectAsStateWithLifecycle() @@ -152,15 +146,6 @@ fun ConnectionsScreen( } } - // State for the device scan dialog - var showScanDialog by remember { mutableStateOf(false) } - val scanResults by scanModel.scanResult.observeAsState(emptyMap()) - - // Observe scan results to show the dialog - if (scanResults.isNotEmpty()) { - showScanDialog = true - } - LaunchedEffect(connectionState, regionUnset) { when (connectionState) { ConnectionState.CONNECTED -> { @@ -245,7 +230,11 @@ fun ConnectionsScreen( DeviceType.BLE -> { BLEDevices( connectionState = connectionState, - btDevices = bleDevices, + bondedDevices = bondedBleDevices, + availableDevices = + scannedBleDevices.values.toList().filterNot { available -> + bondedBleDevices.any { it.address == available.address } + }, selectedDevice = selectedDevice, scanModel = scanModel, bluetoothEnabled = bluetoothState.enabled, @@ -280,7 +269,7 @@ fun ConnectionsScreen( val showWarningNotPaired = !connectionState.isConnected() && !hasShownNotPairedWarning && - bleDevices.none { it is DeviceListEntry.Ble && it.bonded } + bondedBleDevices.none { it is DeviceListEntry.Ble && it.bonded } if (showWarningNotPaired) { Text( text = stringResource(R.string.warning_not_paired), @@ -294,55 +283,6 @@ fun ConnectionsScreen( } } } - - // Compose Device Scan Dialog - if (showScanDialog) { - Dialog( - onDismissRequest = { - showScanDialog = false - scanModel.clearScanResults() - }, - ) { - Surface(shape = MaterialTheme.shapes.medium) { - Column(modifier = Modifier.padding(16.dp)) { - Text( - text = "Select a Bluetooth device", - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(bottom = 16.dp), - ) - Column(modifier = Modifier.selectableGroup()) { - scanResults.values.forEach { device -> - Row( - modifier = - Modifier.fillMaxWidth() - .selectable( - selected = false, // No pre-selection in this dialog - onClick = { - scanModel.onSelected(device) - scanModel.clearScanResults() - showScanDialog = false - }, - ) - .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text(text = device.name) - } - } - } - Spacer(modifier = Modifier.height(16.dp)) - TextButton( - onClick = { - scanModel.clearScanResults() - showScanDialog = false - }, - ) { - Text(stringResource(R.string.cancel)) - } - } - } - } - } } Box(modifier = Modifier.fillMaxWidth().padding(8.dp)) { diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt index 38092eed9..bf9374b8d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt @@ -69,7 +69,8 @@ import org.meshtastic.core.ui.component.TitledCard @Composable fun BLEDevices( connectionState: ConnectionState, - btDevices: List, + bondedDevices: List, + availableDevices: List, selectedDevice: String, scanModel: BTScanModel, bluetoothEnabled: Boolean, @@ -153,7 +154,7 @@ fun BLEDevices( } } - if (btDevices.isEmpty()) { + if (bondedDevices.isEmpty() && availableDevices.isEmpty()) { EmptyStateContent( imageVector = Icons.Rounded.BluetoothDisabled, text = @@ -165,18 +166,19 @@ fun BLEDevices( actionButton = scanButton, ) } else { - TitledCard(title = stringResource(R.string.bluetooth_paired_devices)) { - btDevices.forEach { device -> - val connected = - connectionState == ConnectionState.CONNECTED && device.fullAddress == selectedDevice - DeviceListItem( - connected = connected, - device = device, - onSelect = { scanModel.onSelected(device) }, - modifier = Modifier, - ) - } - } + bondedDevices.Section( + title = stringResource(R.string.bluetooth_paired_devices), + connectionState = connectionState, + selectedDevice = selectedDevice, + onSelect = scanModel::onSelected, + ) + + availableDevices.Section( + title = stringResource(R.string.bluetooth_available_devices), + connectionState = connectionState, + selectedDevice = selectedDevice, + onSelect = scanModel::onSelected, + ) scanButton() } @@ -213,3 +215,25 @@ private fun checkPermissionsAndScan( permissionsState.launchMultiplePermissionRequest() } } + +@Composable +private fun List.Section( + title: String, + connectionState: ConnectionState, + selectedDevice: String, + onSelect: (DeviceListEntry) -> Unit, +) { + if (isNotEmpty()) { + TitledCard(title = title) { + forEach { device -> + val connected = connectionState == ConnectionState.CONNECTED && device.fullAddress == selectedDevice + DeviceListItem( + connected = connected, + device = device, + onSelect = { onSelect(device) }, + modifier = Modifier, + ) + } + } + } +} diff --git a/core/strings/src/main/res/values/strings.xml b/core/strings/src/main/res/values/strings.xml index d91d4913d..a231f5a4d 100644 --- a/core/strings/src/main/res/values/strings.xml +++ b/core/strings/src/main/res/values/strings.xml @@ -821,7 +821,8 @@ No PAX metrics logs available. WiFi Devices BLE Devices - Paired Devices + Paired devices + Available devices Connected Device Rate Limit Exceeded. Please try again later.