diff --git a/app/src/main/java/com/geeksville/mesh/repository/network/NetworkRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/network/NetworkRepository.kt
index ca7308c4c..5f64e2119 100644
--- a/app/src/main/java/com/geeksville/mesh/repository/network/NetworkRepository.kt
+++ b/app/src/main/java/com/geeksville/mesh/repository/network/NetworkRepository.kt
@@ -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 .
*/
-
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")
}
diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt b/app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt
index 4caba1c17..926db854b 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt
@@ -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() {
diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
index 8a19e05b0..f9a714b3a 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
@@ -160,6 +160,7 @@ class MeshService : Service() {
}
return if (!wantForeground) {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
+ stopSelf()
START_NOT_STICKY
} else {
START_STICKY
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 e639d3f7a..1c7acf82a 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
@@ -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 .
*/
-
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),
+ )
}
}
}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/ConnectingDeviceInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/ConnectingDeviceInfo.kt
new file mode 100644
index 000000000..a21c06c8a
--- /dev/null
+++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/ConnectingDeviceInfo.kt
@@ -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 .
+ */
+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))
+ }
+ }
+}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/EmptyStateContent.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/EmptyStateContent.kt
index c25c83b0b..2a0b572e2 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/EmptyStateContent.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/EmptyStateContent.kt
@@ -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 .
*/
-
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") }
}
}
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 6d580eed1..8c3dc1bae 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
@@ -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 .
*/
-
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() }
diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/NodeInfoWriteDataSource.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/NodeInfoWriteDataSource.kt
index fb0d8fe0e..c4ced500c 100644
--- a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/NodeInfoWriteDataSource.kt
+++ b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/NodeInfoWriteDataSource.kt
@@ -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 .
*/
-
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)
diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/SwitchingNodeInfoWriteDataSource.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/SwitchingNodeInfoWriteDataSource.kt
index 872b47ae6..c201cab03 100644
--- a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/SwitchingNodeInfoWriteDataSource.kt
+++ b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/SwitchingNodeInfoWriteDataSource.kt
@@ -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 .
*/
-
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) } }
diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt
index 781259892..8618ebd0f 100644
--- a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt
+++ b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt
@@ -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)
diff --git a/core/strings/src/commonMain/composeResources/values/strings.xml b/core/strings/src/commonMain/composeResources/values/strings.xml
index 73bdbc3f1..dc6216732 100644
--- a/core/strings/src/commonMain/composeResources/values/strings.xml
+++ b/core/strings/src/commonMain/composeResources/values/strings.xml
@@ -217,6 +217,7 @@
Ethernet IP:
Connecting
Not connected
+ No device selected
Connected to radio, but it is sleeping
Application update required
You must update this application on the app store (or Github). It is too old to talk to this radio firmware. Please read our docs on this topic.