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.