mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(connections): Improve connection screen UI and logic (#4224)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
2f3d94c759
commit
5a59dcf2e2
11 changed files with 199 additions and 81 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.repository.network
|
||||
|
||||
import android.net.ConnectivityManager
|
||||
|
|
@ -47,7 +46,8 @@ constructor(
|
|||
private const val SERVICE_TYPE = "_meshtastic._tcp"
|
||||
|
||||
fun NsdServiceInfo.toAddressString() = buildString {
|
||||
append(@Suppress("DEPRECATION") host.toString().substring(1))
|
||||
@Suppress("DEPRECATION")
|
||||
append(host.hostAddress)
|
||||
if (serviceType.trim('.') == SERVICE_TYPE && port != SERVICE_PORT) {
|
||||
append(":$port")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ constructor(
|
|||
serviceBroadcasts.broadcastConnection()
|
||||
Logger.d { "Starting connect" }
|
||||
connectTimeMsec = System.currentTimeMillis()
|
||||
scope.handledLaunch { nodeRepository.clearMyNodeInfo() }
|
||||
startConfigOnly()
|
||||
}
|
||||
|
||||
|
|
@ -219,6 +220,7 @@ constructor(
|
|||
commandSender.sendAdmin(myNodeNum) {
|
||||
setTimeOnly = (System.currentTimeMillis() / MILLISECONDS_IN_SECOND).toInt()
|
||||
}
|
||||
updateStatusNotification()
|
||||
}
|
||||
|
||||
private fun reportConnection() {
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ class MeshService : Service() {
|
|||
}
|
||||
return if (!wantForeground) {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
START_NOT_STICKY
|
||||
} else {
|
||||
START_STICKY
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,31 +14,27 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.connections
|
||||
|
||||
import android.net.InetAddresses
|
||||
import android.os.Build
|
||||
import android.util.Patterns
|
||||
import androidx.compose.animation.Crossfade
|
||||
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.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Language
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularWavyProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
|
|
@ -52,8 +48,6 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -63,8 +57,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.model.BTScanModel
|
||||
import com.geeksville.mesh.model.DeviceListEntry
|
||||
import com.geeksville.mesh.ui.connections.components.BLEDevices
|
||||
import com.geeksville.mesh.ui.connections.components.ConnectingDeviceInfo
|
||||
import com.geeksville.mesh.ui.connections.components.ConnectionsSegmentedBar
|
||||
import com.geeksville.mesh.ui.connections.components.CurrentlyConnectedInfo
|
||||
import com.geeksville.mesh.ui.connections.components.EmptyStateContent
|
||||
import com.geeksville.mesh.ui.connections.components.NetworkDevices
|
||||
import com.geeksville.mesh.ui.connections.components.UsbDevices
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
|
|
@ -81,23 +77,28 @@ import org.meshtastic.core.strings.connected_sleeping
|
|||
import org.meshtastic.core.strings.connecting
|
||||
import org.meshtastic.core.strings.connections
|
||||
import org.meshtastic.core.strings.must_set_region
|
||||
import org.meshtastic.core.strings.no_device_selected
|
||||
import org.meshtastic.core.strings.not_connected
|
||||
import org.meshtastic.core.strings.set_your_region
|
||||
import org.meshtastic.core.strings.warning_not_paired
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.component.MainAppBar
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.NoDevice
|
||||
import org.meshtastic.feature.settings.navigation.ConfigRoute
|
||||
import org.meshtastic.feature.settings.navigation.getNavRouteFrom
|
||||
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.feature.settings.radio.component.PacketResponseStateDialog
|
||||
import org.meshtastic.proto.ConfigProtos
|
||||
|
||||
fun String?.isIPAddress(): Boolean = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
fun String?.isValidAddress(): Boolean = if (this.isNullOrBlank()) {
|
||||
false
|
||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
@Suppress("DEPRECATION")
|
||||
this != null && Patterns.IP_ADDRESS.matcher(this).matches()
|
||||
Patterns.IP_ADDRESS.matcher(this).matches() || Patterns.DOMAIN_NAME.matcher(this).matches()
|
||||
} else {
|
||||
InetAddresses.isNumericAddress(this.toString())
|
||||
InetAddresses.isNumericAddress(this) || Patterns.DOMAIN_NAME.matcher(this).matches()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -125,7 +126,6 @@ fun ConnectionsScreen(
|
|||
val ourNode by connectionsViewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
val selectedDevice by scanModel.selectedNotNullFlow.collectAsStateWithLifecycle()
|
||||
val bluetoothState by connectionsViewModel.bluetoothState.collectAsStateWithLifecycle()
|
||||
val density = LocalDensity.current
|
||||
val regionUnset = config.lora.region == ConfigProtos.Config.LoRaConfig.RegionCode.UNSET
|
||||
|
||||
val bleDevices by scanModel.bleDevicesForUi.collectAsStateWithLifecycle()
|
||||
|
|
@ -197,50 +197,76 @@ fun ConnectionsScreen(
|
|||
.padding(paddingValues)
|
||||
.padding(16.dp),
|
||||
) {
|
||||
var connectionSectionHeight by remember { mutableStateOf(0.dp) }
|
||||
val placeholderHeight = connectionSectionHeight.takeIf { it > 0.dp } ?: 0.dp
|
||||
Box(modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp).heightIn(min = placeholderHeight)) {
|
||||
if (connectionState == ConnectionState.Connecting) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().align(Alignment.Center),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
CircularWavyProgressIndicator(modifier = Modifier.size(96.dp).padding(16.dp))
|
||||
}
|
||||
}
|
||||
androidx.compose.animation.AnimatedVisibility(visible = connectionState.isConnected()) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().onSizeChanged { size ->
|
||||
if (connectionState.isConnected()) {
|
||||
connectionSectionHeight = with(density) { size.height.toDp() }
|
||||
}
|
||||
},
|
||||
) {
|
||||
ourNode?.let { node ->
|
||||
TitledCard(title = stringResource(Res.string.connected_device)) {
|
||||
CurrentlyConnectedInfo(
|
||||
node = node,
|
||||
bleDevice =
|
||||
bleDevices.firstOrNull { it.fullAddress == selectedDevice }
|
||||
as DeviceListEntry.Ble?,
|
||||
onNavigateToNodeDetails = onNavigateToNodeDetails,
|
||||
onClickDisconnect = { scanModel.disconnect() },
|
||||
)
|
||||
}
|
||||
}
|
||||
val uiState =
|
||||
when {
|
||||
connectionState.isConnected() && ourNode != null -> 2
|
||||
connectionState == ConnectionState.Connecting ||
|
||||
(connectionState == ConnectionState.Disconnected && selectedDevice != "n") -> 1
|
||||
|
||||
if (regionUnset && selectedDevice != "m") {
|
||||
TitledCard(title = null) {
|
||||
ListItem(
|
||||
leadingIcon = Icons.Rounded.Language,
|
||||
text = stringResource(Res.string.set_your_region),
|
||||
) {
|
||||
isWaiting = true
|
||||
radioConfigViewModel.setResponseStateLoading(ConfigRoute.LORA)
|
||||
else -> 0
|
||||
}
|
||||
|
||||
Crossfade(
|
||||
targetState = uiState,
|
||||
label = "connection_state",
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
) { state ->
|
||||
when (state) {
|
||||
2 -> {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
ourNode?.let { node ->
|
||||
TitledCard(title = stringResource(Res.string.connected_device)) {
|
||||
CurrentlyConnectedInfo(
|
||||
node = node,
|
||||
bleDevice =
|
||||
bleDevices.firstOrNull { it.fullAddress == selectedDevice }
|
||||
as DeviceListEntry.Ble?,
|
||||
onNavigateToNodeDetails = onNavigateToNodeDetails,
|
||||
onClickDisconnect = { scanModel.disconnect() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (regionUnset && selectedDevice != "m") {
|
||||
TitledCard(title = null) {
|
||||
ListItem(
|
||||
leadingIcon = Icons.Rounded.Language,
|
||||
text = stringResource(Res.string.set_your_region),
|
||||
) {
|
||||
isWaiting = true
|
||||
radioConfigViewModel.setResponseStateLoading(ConfigRoute.LORA)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1 -> {
|
||||
val selectedEntry =
|
||||
bleDevices.find { it.fullAddress == selectedDevice }
|
||||
?: discoveredTcpDevices.find { it.fullAddress == selectedDevice }
|
||||
?: recentTcpDevices.find { it.fullAddress == selectedDevice }
|
||||
?: usbDevices.find { it.fullAddress == selectedDevice }
|
||||
|
||||
val name = selectedEntry?.name ?: "Unknown Device"
|
||||
val address = selectedEntry?.address ?: selectedDevice
|
||||
|
||||
TitledCard(title = stringResource(Res.string.connected_device)) {
|
||||
ConnectingDeviceInfo(
|
||||
deviceName = name,
|
||||
deviceAddress = address,
|
||||
onClickDisconnect = { scanModel.disconnect() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
EmptyStateContent(
|
||||
imageVector = MeshtasticIcons.NoDevice,
|
||||
text = stringResource(Res.string.no_device_selected),
|
||||
modifier = Modifier.height(160.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularWavyProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.connecting
|
||||
import org.meshtastic.core.strings.disconnect
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun ConnectingDeviceInfo(
|
||||
deviceName: String,
|
||||
deviceAddress: String,
|
||||
onClickDisconnect: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
CircularWavyProgressIndicator(modifier = Modifier.size(40.dp))
|
||||
|
||||
Column {
|
||||
Text(text = deviceName, style = MaterialTheme.typography.titleMedium)
|
||||
Text(text = deviceAddress, style = MaterialTheme.typography.bodySmall)
|
||||
Text(text = stringResource(Res.string.connecting), style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
shape = RectangleShape,
|
||||
modifier = Modifier.fillMaxWidth().height(40.dp),
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.StatusRed,
|
||||
contentColor = Color.White,
|
||||
),
|
||||
onClick = onClickDisconnect,
|
||||
) {
|
||||
Text(stringResource(Res.string.disconnect))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.connections.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -39,9 +38,14 @@ import androidx.compose.ui.unit.dp
|
|||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun EmptyStateContent(imageVector: ImageVector? = null, text: String, actionButton: @Composable (() -> Unit)? = null) {
|
||||
fun EmptyStateContent(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
imageVector: ImageVector? = null,
|
||||
actionButton: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
|
|
@ -63,7 +67,7 @@ fun EmptyStateContent(imageVector: ImageVector? = null, text: String, actionButt
|
|||
fun EmptyStateContentPreview() {
|
||||
AppTheme {
|
||||
Surface {
|
||||
EmptyStateContent(imageVector = Icons.Rounded.BluetoothDisabled, text = "No devices found") {
|
||||
EmptyStateContent(text = "No devices found", imageVector = Icons.Rounded.BluetoothDisabled) {
|
||||
Button(onClick = {}) { Text("Button") }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.connections.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -52,17 +51,17 @@ import androidx.compose.ui.unit.dp
|
|||
import com.geeksville.mesh.model.BTScanModel
|
||||
import com.geeksville.mesh.model.DeviceListEntry
|
||||
import com.geeksville.mesh.repository.network.NetworkRepository
|
||||
import com.geeksville.mesh.ui.connections.isIPAddress
|
||||
import com.geeksville.mesh.ui.connections.isValidAddress
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.service.ConnectionState
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.add_network_device
|
||||
import org.meshtastic.core.strings.address
|
||||
import org.meshtastic.core.strings.cancel
|
||||
import org.meshtastic.core.strings.confirm_forget_connection
|
||||
import org.meshtastic.core.strings.discovered_network_devices
|
||||
import org.meshtastic.core.strings.forget_connection
|
||||
import org.meshtastic.core.strings.ip_address
|
||||
import org.meshtastic.core.strings.ip_port
|
||||
import org.meshtastic.core.strings.no_network_devices
|
||||
import org.meshtastic.core.strings.recent_network_devices
|
||||
|
|
@ -89,8 +88,8 @@ fun NetworkDevices(
|
|||
AddDeviceDialog(
|
||||
searchDialogState,
|
||||
onHideDialog = { showSearchDialog = false },
|
||||
onClickAdd = { ipAddress, fullAddress ->
|
||||
scanModel.onSelected(DeviceListEntry.Tcp(ipAddress, fullAddress))
|
||||
onClickAdd = { address, fullAddress ->
|
||||
scanModel.onSelected(DeviceListEntry.Tcp(address, fullAddress))
|
||||
showSearchDialog = false
|
||||
},
|
||||
)
|
||||
|
|
@ -163,9 +162,9 @@ fun NetworkDevices(
|
|||
private fun AddDeviceDialog(
|
||||
sheetState: SheetState,
|
||||
onHideDialog: () -> Unit,
|
||||
onClickAdd: (ipAddress: String, fullAddress: String) -> Unit,
|
||||
onClickAdd: (address: String, fullAddress: String) -> Unit,
|
||||
) {
|
||||
val ipState = rememberTextFieldState("")
|
||||
val addressState = rememberTextFieldState("")
|
||||
val portState = rememberTextFieldState(NetworkRepository.SERVICE_PORT.toString())
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -175,11 +174,11 @@ private fun AddDeviceDialog(
|
|||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(
|
||||
state = ipState,
|
||||
state = addressState,
|
||||
labelPosition = TextFieldLabelPosition.Above(),
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
label = { Text(stringResource(Res.string.ip_address)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal, imeAction = ImeAction.Next),
|
||||
label = { Text(stringResource(Res.string.address)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Next),
|
||||
modifier = Modifier.weight(.7f),
|
||||
)
|
||||
|
||||
|
|
@ -202,18 +201,18 @@ private fun AddDeviceDialog(
|
|||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
val ipAddress = ipState.text.toString()
|
||||
if (ipAddress.isIPAddress()) {
|
||||
val address = addressState.text.toString()
|
||||
if (address.isValidAddress()) {
|
||||
val portString = portState.text.toString()
|
||||
|
||||
val combinedString =
|
||||
if (portString.isNotEmpty() && portString.toInt() != NetworkRepository.SERVICE_PORT) {
|
||||
"$ipAddress:$portString"
|
||||
"$address:$portString"
|
||||
} else {
|
||||
ipAddress
|
||||
address
|
||||
}
|
||||
|
||||
onClickAdd(ipState.text.toString(), "t$combinedString")
|
||||
onClickAdd(addressState.text.toString(), "t$combinedString")
|
||||
|
||||
scope
|
||||
.launch { sheetState.hide() }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.data.datasource
|
||||
|
||||
import org.meshtastic.core.database.entity.MetadataEntity
|
||||
|
|
@ -28,6 +27,8 @@ interface NodeInfoWriteDataSource {
|
|||
|
||||
suspend fun clearNodeDB(preserveFavorites: Boolean)
|
||||
|
||||
suspend fun clearMyNodeInfo()
|
||||
|
||||
suspend fun deleteNode(num: Int)
|
||||
|
||||
suspend fun deleteNodes(nodeNums: List<Int>)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.data.datasource
|
||||
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -43,6 +42,9 @@ constructor(
|
|||
override suspend fun clearNodeDB(preserveFavorites: Boolean) =
|
||||
withContext(dispatchers.io) { dbManager.withDb { it.nodeInfoDao().clearNodeInfo(preserveFavorites) } }
|
||||
|
||||
override suspend fun clearMyNodeInfo() =
|
||||
withContext(dispatchers.io) { dbManager.withDb { it.nodeInfoDao().clearMyNodeInfo() } }
|
||||
|
||||
override suspend fun deleteNode(num: Int) =
|
||||
withContext(dispatchers.io) { dbManager.withDb { it.nodeInfoDao().deleteNode(num) } }
|
||||
|
||||
|
|
|
|||
|
|
@ -150,6 +150,8 @@ constructor(
|
|||
suspend fun clearNodeDB(preserveFavorites: Boolean = false) =
|
||||
withContext(dispatchers.io) { nodeInfoWriteDataSource.clearNodeDB(preserveFavorites) }
|
||||
|
||||
suspend fun clearMyNodeInfo() = withContext(dispatchers.io) { nodeInfoWriteDataSource.clearMyNodeInfo() }
|
||||
|
||||
suspend fun deleteNode(num: Int) = withContext(dispatchers.io) {
|
||||
nodeInfoWriteDataSource.deleteNode(num)
|
||||
nodeInfoWriteDataSource.deleteMetadata(num)
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@
|
|||
<string name="ethernet_ip">Ethernet IP:</string>
|
||||
<string name="connecting">Connecting</string>
|
||||
<string name="not_connected">Not connected</string>
|
||||
<string name="no_device_selected">No device selected</string>
|
||||
<string name="connected_sleeping">Connected to radio, but it is sleeping</string>
|
||||
<string name="app_too_old">Application update required</string>
|
||||
<string name="must_update">You must update this application on the app store (or Github). It is too old to talk to this radio firmware. Please read our <a href="https://meshtastic.org/docs/software/android/installation">docs</a> on this topic.</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue