diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt index 23ffc7cb8..28c20f4f3 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt @@ -359,6 +359,11 @@ fun ConnectionsScreen( } var selectedDeviceType by remember { mutableStateOf(DeviceType.BLE) } + LaunchedEffect(selectedDevice) { + // Determine based on the selected device - if disconnected, keep the last selected type + selectedDeviceType = + selectedDevice.let { DeviceType.fromAddress(it) } ?: selectedDeviceType + } SingleChoiceSegmentedButtonRow( modifier = Modifier.fillMaxWidth(), ) { @@ -709,7 +714,25 @@ private tailrec fun Context.findActivity(): Activity = when (this) { private enum class DeviceType { BLE, TCP, - USB, + USB; + + companion object { + fun fromAddress(address: String): DeviceType? { + val prefix = address[0] + val isBLE: Boolean = prefix == 'x' + val isUSB: Boolean = prefix == 's' + val isTCP: Boolean = prefix == 't' + val isMock: Boolean = prefix == 'm' + val isDisconnect: Boolean = prefix == 'n' + return when { + isBLE -> BLE + isUSB -> USB + isTCP -> TCP + isMock -> USB // Treat mock as USB for UI purposes + else -> null + } + } + } } private const val SCAN_PERIOD: Long = 10000 // 10 seconds diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/NetworkDevices.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/NetworkDevices.kt index e949c9494..ee9500712 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/NetworkDevices.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/NetworkDevices.kt @@ -17,28 +17,28 @@ package com.geeksville.mesh.ui.connections.components +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.WifiFind +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp @@ -47,6 +47,7 @@ import com.geeksville.mesh.model.BTScanModel import com.geeksville.mesh.repository.network.NetworkRepository import com.geeksville.mesh.ui.connections.isIPAddress +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Suppress("MagicNumber", "LongMethod") @Composable fun NetworkDevices( @@ -86,56 +87,62 @@ fun NetworkDevices( ) } } + Row( modifier = Modifier .fillMaxWidth() - .selectable( - selected = ("t$manualIpAddress:$manualIpPort" == selectedDevice), - onClick = { - if (manualIpAddress.text.toString().isIPAddress()) { - scanModel.onSelected( - BTScanModel.DeviceListEntry( - "", - "t$manualIpAddress:$manualIpPort", - true - ) - ) - } - }, - enabled = manualIpAddress.text.toString().isIPAddress(), - role = Role.Companion.RadioButton - ) .padding(8.dp), - verticalAlignment = Alignment.Companion.CenterVertically + verticalAlignment = Alignment.Companion.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally) ) { - RadioButton( - selected = ("t$manualIpAddress:$manualIpPort" == selectedDevice), - onClick = null, - enabled = manualIpAddress.toString().isIPAddress() - ) OutlinedTextField( state = manualIpAddress, lineLimits = TextFieldLineLimits.SingleLine, label = { Text(stringResource(R.string.ip_address)) }, keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Companion.Number, - imeAction = ImeAction.Done + keyboardType = KeyboardType.Companion.Decimal, + imeAction = ImeAction.Next ), - modifier = Modifier - .weight(0.7f) - .padding(start = 16.dp) + modifier = Modifier.weight(.7f, fill = false) // Fill 70% of the space ) OutlinedTextField( state = manualIpPort, + placeholder = { Text(NetworkRepository.SERVICE_PORT.toString()) }, lineLimits = TextFieldLineLimits.SingleLine, label = { Text(stringResource(R.string.ip_port)) }, keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Companion.Number, + keyboardType = KeyboardType.Companion.Decimal, imeAction = ImeAction.Done ), - modifier = Modifier - .weight(weight = 0.3f) - .padding(start = 8.dp) + modifier = Modifier.weight(.3f, fill = false) // Fill remaining space ) + IconButton( + onClick = { + if (manualIpAddress.text.toString().isIPAddress()) { + val fullAddress = + "t" + if ( + manualIpPort.text.isNotEmpty() && + manualIpPort.text.toString().toInt() != NetworkRepository.SERVICE_PORT + ) { + "${manualIpAddress.text}:${manualIpPort.text}" + } else { + "${manualIpAddress.text}" + } + scanModel.onSelected( + BTScanModel.DeviceListEntry( + "${manualIpAddress.text}", + fullAddress, + true + ) + ) + } + } + ) { + Icon( + imageVector = Icons.Default.WifiFind, + contentDescription = stringResource(R.string.add), + modifier = Modifier.size(32.dp) + ) + } } }