diff --git a/app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt b/app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt index 8f262c47c..37c19f477 100644 --- a/app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt +++ b/app/src/test/kotlin/org/meshtastic/app/service/Fakes.kt @@ -16,7 +16,6 @@ */ package org.meshtastic.app.service -import android.app.Notification import dev.mokkery.MockMode import dev.mokkery.mock import org.meshtastic.core.model.Node @@ -37,7 +36,7 @@ class FakeMeshServiceNotifications : MeshServiceNotifications { override fun updateServiceStateNotification( state: org.meshtastic.core.model.ConnectionState, telemetry: Telemetry?, - ): Notification = mock(MockMode.autofill) + ) {} override suspend fun updateMessageNotification( contactKey: String, diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt index fde8841ce..dbf07fdaf 100644 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt +++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt @@ -347,11 +347,12 @@ class MeshConnectionManagerImpl( updateStatusNotification(t) } - override fun updateStatusNotification(telemetry: Telemetry?): Any = + override fun updateStatusNotification(telemetry: Telemetry?) { serviceNotifications.updateServiceStateNotification( serviceRepository.connectionState.value, telemetry = telemetry, ) + } companion object { private const val DEVICE_SLEEP_TIMEOUT_SECONDS = 30 diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionState.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionState.kt index 0af5a0efd..505f187ea 100644 --- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionState.kt +++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionState.kt @@ -28,12 +28,4 @@ sealed class ConnectionState { /** The device is in a light sleep state, and we are waiting for it to wake up and reconnect to us. */ data object DeviceSleep : ConnectionState() - - fun isConnected() = this == Connected - - fun isConnecting() = this == Connecting - - fun isDisconnected() = this == Disconnected - - fun isDeviceSleep() = this == DeviceSleep } diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConnectionManager.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConnectionManager.kt index c60db9afa..9f9851072 100644 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConnectionManager.kt +++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConnectionManager.kt @@ -35,6 +35,6 @@ interface MeshConnectionManager { /** Updates the telemetry information for the local node. */ fun updateTelemetry(t: Telemetry) - /** Updates and returns the current status notification. */ - fun updateStatusNotification(telemetry: Telemetry? = null): Any + /** Updates the current status notification. */ + fun updateStatusNotification(telemetry: Telemetry? = null) } diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshServiceNotifications.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshServiceNotifications.kt index 195a241ee..30aade866 100644 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshServiceNotifications.kt +++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshServiceNotifications.kt @@ -28,7 +28,7 @@ interface MeshServiceNotifications { fun initChannels() - fun updateServiceStateNotification(state: org.meshtastic.core.model.ConnectionState, telemetry: Telemetry?): Any + fun updateServiceStateNotification(state: org.meshtastic.core.model.ConnectionState, telemetry: Telemetry?) suspend fun updateMessageNotification( contactKey: String, diff --git a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshService.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshService.kt index 701ca2d69..05f1135f1 100644 --- a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshService.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshService.kt @@ -42,6 +42,7 @@ import org.meshtastic.core.repository.CommandSender import org.meshtastic.core.repository.MeshConnectionManager import org.meshtastic.core.repository.MeshLocationManager import org.meshtastic.core.repository.MeshRouter +import org.meshtastic.core.repository.MeshServiceNotifications import org.meshtastic.core.repository.NodeManager import org.meshtastic.core.repository.RadioInterfaceService import org.meshtastic.core.repository.SERVICE_NOTIFY_ID @@ -67,6 +68,12 @@ class MeshService : Service() { private val connectionManager: MeshConnectionManager by inject() + private val notifications: MeshServiceNotifications by inject() + + /** Android-typed accessor for the foreground service notification. */ + private val androidNotifications: MeshServiceNotificationsImpl + get() = notifications as MeshServiceNotificationsImpl + private val orchestrator: MeshServiceOrchestrator by inject() private val router: MeshRouter by inject() @@ -130,7 +137,8 @@ class MeshService : Service() { val a = radioInterfaceService.getDeviceAddress() val wantForeground = a != null && a != "n" - val notification = connectionManager.updateStatusNotification() as android.app.Notification + connectionManager.updateStatusNotification() + val notification = androidNotifications.getServiceNotification() val foregroundServiceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { diff --git a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt index 05fe1d3b4..75bbe27ce 100644 --- a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt @@ -288,13 +288,25 @@ class MeshServiceNotificationsImpl( private var cachedLocalStats: LocalStats? = null private var nextStatsUpdateMillis: Long = 0 private var cachedMessage: String? = null + private var cachedServiceNotification: Notification? = null + + /** + * Returns the last-built service state notification, or builds a default one if none exists. This is used by + * [MeshService] for [android.app.Service.startForeground]. + */ + fun getServiceNotification(): Notification = cachedServiceNotification + ?: createServiceStateNotification( + name = getString(Res.string.meshtastic_app_name), + message = null, + nextUpdateAt = 0, + ) // region Public Notification Methods @Suppress("CyclomaticComplexMethod", "NestedBlockDepth") override fun updateServiceStateNotification( state: org.meshtastic.core.model.ConnectionState, telemetry: Telemetry?, - ): Notification { + ) { val summaryString = when (state) { is org.meshtastic.core.model.ConnectionState.Connected -> @@ -357,8 +369,8 @@ class MeshServiceNotificationsImpl( message = cachedMessage, nextUpdateAt = nextStatsUpdateMillis, ) + cachedServiceNotification = notification notificationManager.notify(SERVICE_NOTIFY_ID, notification) - return notification } override suspend fun updateMessageNotification( diff --git a/core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeMeshServiceNotifications.kt b/core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeMeshServiceNotifications.kt index c90e69da9..dc36b9956 100644 --- a/core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeMeshServiceNotifications.kt +++ b/core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeMeshServiceNotifications.kt @@ -31,7 +31,7 @@ class FakeMeshServiceNotifications : MeshServiceNotifications { override fun updateServiceStateNotification( state: org.meshtastic.core.model.ConnectionState, telemetry: Telemetry?, - ): Any = Any() + ) {} override suspend fun updateMessageNotification( contactKey: String, diff --git a/desktop/src/main/kotlin/org/meshtastic/desktop/notification/DesktopMeshServiceNotifications.kt b/desktop/src/main/kotlin/org/meshtastic/desktop/notification/DesktopMeshServiceNotifications.kt index 061da246d..a5ec5b795 100644 --- a/desktop/src/main/kotlin/org/meshtastic/desktop/notification/DesktopMeshServiceNotifications.kt +++ b/desktop/src/main/kotlin/org/meshtastic/desktop/notification/DesktopMeshServiceNotifications.kt @@ -45,9 +45,8 @@ class DesktopMeshServiceNotifications(private val notificationManager: Notificat override fun updateServiceStateNotification( state: org.meshtastic.core.model.ConnectionState, telemetry: Telemetry?, - ): Any { + ) { // We don't have a foreground service on desktop - return Unit } override suspend fun updateMessageNotification( diff --git a/desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt b/desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt index 563571ef6..8d53990e2 100644 --- a/desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt +++ b/desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt @@ -114,7 +114,7 @@ class NoopMeshServiceNotifications : MeshServiceNotifications { override fun updateServiceStateNotification( state: org.meshtastic.core.model.ConnectionState, telemetry: Telemetry?, - ): Any = Unit + ) {} override suspend fun updateMessageNotification( contactKey: String, diff --git a/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt b/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt index 828b7be2f..441b81c84 100644 --- a/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt +++ b/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt @@ -151,7 +151,7 @@ fun ConnectionsScreen( MainAppBar( title = stringResource(Res.string.connections), ourNode = ourNode, - showNodeChip = ourNode != null && connectionState.isConnected(), + showNodeChip = ourNode != null && connectionState is ConnectionState.Connected, canNavigateUp = false, onNavigateUp = {}, actions = {}, @@ -167,8 +167,8 @@ fun ConnectionsScreen( Spacer(modifier = Modifier.height(4.dp)) val uiState = when { - connectionState.isConnected() && ourNode != null -> 2 - connectionState.isConnected() || + connectionState is ConnectionState.Connected && ourNode != null -> 2 + connectionState is ConnectionState.Connected || connectionState == ConnectionState.Connecting || selectedDevice != NO_DEVICE_SELECTED -> 1 diff --git a/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/ConnectingDeviceInfo.kt b/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/ConnectingDeviceInfo.kt index 9c86a17bf..53cec80b5 100644 --- a/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/ConnectingDeviceInfo.kt +++ b/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/ConnectingDeviceInfo.kt @@ -51,7 +51,7 @@ fun ConnectingDeviceInfo( modifier: Modifier = Modifier, ) { val statusText = - if (connectionState.isConnected()) { + if (connectionState is ConnectionState.Connected) { stringResource(Res.string.connected) } else { stringResource(Res.string.connecting) diff --git a/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/DeviceListItem.kt b/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/DeviceListItem.kt index 7071c18c9..8499d4e20 100644 --- a/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/DeviceListItem.kt +++ b/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/DeviceListItem.kt @@ -90,9 +90,9 @@ fun DeviceListItem( val icon = when (device) { is DeviceListEntry.Ble -> - if (connectionState.isConnected()) { + if (connectionState is ConnectionState.Connected) { MeshtasticIcons.BluetoothConnected - } else if (connectionState.isConnecting()) { + } else if (connectionState is ConnectionState.Connecting) { MeshtasticIcons.BluetoothSearching } else { MeshtasticIcons.Bluetooth @@ -132,7 +132,7 @@ fun DeviceListItem( contentDescription = contentDescription, modifier = Modifier.size(32.dp), tint = - if (connectionState.isConnected()) { + if (connectionState is ConnectionState.Connected) { MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.onSurfaceVariant @@ -146,10 +146,10 @@ fun DeviceListItem( Rssi(rssi = displayedRssi) } - if (connectionState.isConnecting()) { + if (connectionState is ConnectionState.Connecting) { CircularProgressIndicator(modifier = Modifier.size(32.dp)) } else { - RadioButton(selected = connectionState.isConnected(), onClick = null) + RadioButton(selected = connectionState is ConnectionState.Connected, onClick = null) } } }, diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt index 8d9236a8a..8cc621e1c 100644 --- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt +++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt @@ -65,6 +65,7 @@ import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource import org.meshtastic.core.common.util.HomoglyphCharacterStringTransformer import org.meshtastic.core.database.entity.QuickChatAction +import org.meshtastic.core.model.ConnectionState import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.Node import org.meshtastic.core.model.util.getChannel @@ -327,7 +328,7 @@ fun MessageScreen( Column { AnimatedVisibility(visible = showQuickChat) { QuickChatRow( - enabled = connectionState.isConnected(), + enabled = connectionState is ConnectionState.Connected, actions = quickChatActions, onClick = { action -> handleQuickChatAction( @@ -344,7 +345,7 @@ fun MessageScreen( ourNode = ourNode, ) MessageInput( - isEnabled = connectionState.isConnected(), + isEnabled = connectionState is ConnectionState.Connected, isHomoglyphEncodingEnabled = homoglyphEncodingEnabled, textFieldState = messageInputState, onSendMessage = { diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/Contacts.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/Contacts.kt index e522ba0e2..ac6232ac2 100644 --- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/Contacts.kt +++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/Contacts.kt @@ -64,6 +64,7 @@ import org.jetbrains.compose.resources.stringResource import org.meshtastic.core.common.util.MeshtasticUri import org.meshtastic.core.common.util.NumberFormatter import org.meshtastic.core.common.util.nowMillis +import org.meshtastic.core.model.ConnectionState import org.meshtastic.core.model.Contact import org.meshtastic.core.model.ContactSettings import org.meshtastic.core.model.util.TimeConstants @@ -232,7 +233,7 @@ fun ContactsScreen( MainAppBar( title = stringResource(Res.string.conversations), ourNode = ourNode, - showNodeChip = ourNode != null && connectionState.isConnected(), + showNodeChip = ourNode != null && connectionState is ConnectionState.Connected, canNavigateUp = false, onNavigateUp = {}, actions = { @@ -250,7 +251,7 @@ fun ContactsScreen( ) }, floatingActionButton = { - if (connectionState.isConnected()) { + if (connectionState is ConnectionState.Connected) { MeshtasticImportFAB( sharedContact = sharedContactRequested, onImport = { uriString -> diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt index a6c8abfb9..27c57fafe 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt @@ -41,6 +41,7 @@ import org.meshtastic.core.domain.usecase.settings.SetMeshLogSettingsUseCase import org.meshtastic.core.domain.usecase.settings.SetNotificationSettingsUseCase import org.meshtastic.core.domain.usecase.settings.SetProvideLocationUseCase import org.meshtastic.core.domain.usecase.settings.SetThemeUseCase +import org.meshtastic.core.model.ConnectionState import org.meshtastic.core.model.MyNodeInfo import org.meshtastic.core.model.Node import org.meshtastic.core.model.RadioController @@ -84,7 +85,9 @@ class SettingsViewModel( val ourNodeInfo: StateFlow = nodeRepository.ourNodeInfo val isConnected = - radioController.connectionState.map { it.isConnected() }.stateInWhileSubscribed(initialValue = false) + radioController.connectionState + .map { it is ConnectionState.Connected } + .stateInWhileSubscribed(initialValue = false) val localConfig: StateFlow = radioConfigRepository.localConfigFlow.stateInWhileSubscribed(initialValue = LocalConfig())