diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index de50a1908..2a73b11cb 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -434,6 +434,18 @@ class UIViewModel @Inject constructor( ) }.launchIn(viewModelScope) + radioConfigRepository.clientNotification.filterNotNull().onEach { notification -> + showAlert( + title = app.getString(R.string.client_notification), + message = notification.message, + onConfirm = { + radioConfigRepository.clearClientNotification() + meshServiceNotifications.clearClientNotification(notification) + }, + dismissable = false + ) + }.launchIn(viewModelScope) + radioConfigRepository.localConfigFlow.onEach { config -> _localConfig.value = config }.launchIn(viewModelScope) diff --git a/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt index a081a9f68..3382d1c43 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt @@ -25,6 +25,7 @@ import com.geeksville.mesh.ConfigProtos.Config import com.geeksville.mesh.IMeshService import com.geeksville.mesh.LocalOnlyProtos.LocalConfig import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig +import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshProtos.DeviceMetadata import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig @@ -85,6 +86,7 @@ class RadioConfigRepository @Inject constructor( suspend fun installNodeDB(mi: MyNodeEntity, nodes: List) { nodeDB.installNodeDB(mi, nodes) } + suspend fun insertMetadata(fromNum: Int, metadata: DeviceMetadata) { nodeDB.insertMetadata(MetadataEntity(fromNum, metadata)) } @@ -187,6 +189,16 @@ class RadioConfigRepository @Inject constructor( } } + val clientNotification = serviceRepository.clientNotification + + fun setClientNotification(notification: MeshProtos.ClientNotification?) { + serviceRepository.setClientNotification(notification) + } + + fun clearClientNotification() { + serviceRepository.clearClientNotification() + } + val errorMessage: StateFlow get() = serviceRepository.errorMessage fun setErrorMessage(text: String) { 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 6df36d9e9..befa22567 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1723,7 +1723,8 @@ class MeshService : Service(), Logging { private fun handleClientNotification(notification: MeshProtos.ClientNotification) { debug("Received clientNotification ${notification.toOneLineString()}") - radioConfigRepository.setErrorMessage(notification.message) + radioConfigRepository.setClientNotification(notification) + serviceNotifications.showClientNotification(notification) // if the future for the originating request is still in the queue, complete as unsuccessful for now queueResponse.remove(notification.replyId)?.complete(false) } diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt index 719bfb7d8..60c51731c 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt @@ -35,6 +35,7 @@ import androidx.core.app.Person import androidx.core.app.RemoteInput import androidx.core.net.toUri import com.geeksville.mesh.MainActivity +import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.R import com.geeksville.mesh.TelemetryProtos.LocalStats import com.geeksville.mesh.android.notificationManager @@ -69,6 +70,7 @@ class MeshServiceNotifications( createNewNodeNotificationChannel() createLowBatteryNotificationChannel() createLowBatteryRemoteNotificationChannel() + createClientNotificationChannel() } } @@ -233,6 +235,25 @@ class MeshServiceNotifications( return channelId } + @RequiresApi(Build.VERSION_CODES.O) + private fun createClientNotificationChannel(): String { + val channelId = "client_notifications" + if (notificationManager.getNotificationChannel(channelId) == null) { + val channelName = context.getString(R.string.client_notification) + val channel = NotificationChannel( + channelId, + channelName, + NotificationManager.IMPORTANCE_HIGH + ).apply { + lightColor = notificationLightColor + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + setShowBadge(true) + } + notificationManager.createNotificationChannel(channel) + } + return channelId + } + private val channelId: String by lazy { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannel() @@ -287,6 +308,14 @@ class MeshServiceNotifications( } } + private val clientNotificationChannelId: String by lazy { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createClientNotificationChannel() + } else { + "" + } + } + private fun LocalStats?.formatToString(): String = this?.allFields?.mapNotNull { (k, v) -> when (k.name) { "num_online_nodes", "num_total_nodes" -> return@mapNotNull null @@ -351,6 +380,16 @@ class MeshServiceNotifications( notificationManager.cancel(node.num) } + fun showClientNotification(notification: MeshProtos.ClientNotification) { + notificationManager.notify( + notification.toString().hashCode(), // show unique notifications + createClientNotification(context.getString(R.string.client_notification), notification.message) + ) + } + fun clearClientNotification(notification: MeshProtos.ClientNotification) { + notificationManager.cancel(notification.toString().hashCode()) + } + private val openAppIntent: PendingIntent by lazy { PendingIntent.getActivity( context, @@ -583,4 +622,26 @@ class MeshServiceNotifications( return lowBatteryNotificationBuilder.build() } } + + lateinit var clientNotificationBuilder: NotificationCompat.Builder + + private fun createClientNotification(name: String, message: String? = null): Notification { + if (!::clientNotificationBuilder.isInitialized) { + clientNotificationBuilder = commonBuilder(clientNotificationChannelId) + } + with(clientNotificationBuilder) { + priority = NotificationCompat.PRIORITY_DEFAULT + setCategory(Notification.CATEGORY_ERROR) + setAutoCancel(true) + setContentTitle(name) + message?.let { + setContentText(it) + setStyle( + NotificationCompat.BigTextStyle() + .bigText(message), + ) + } + } + return clientNotificationBuilder.build() + } } diff --git a/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt b/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt index 32b931014..69d9dc940 100644 --- a/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt @@ -18,6 +18,7 @@ package com.geeksville.mesh.service import com.geeksville.mesh.IMeshService +import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.android.Logging import kotlinx.coroutines.channels.Channel @@ -32,6 +33,7 @@ import javax.inject.Singleton /** * Repository class for managing the [IMeshService] instance and connection state */ +@Suppress("TooManyFunctions") @Singleton class ServiceRepository @Inject constructor() : Logging { var meshService: IMeshService? = null @@ -49,6 +51,18 @@ class ServiceRepository @Inject constructor() : Logging { _connectionState.value = connectionState } + private val _clientNotification = MutableStateFlow(null) + val clientNotification: StateFlow get() = _clientNotification + fun setClientNotification(notification: MeshProtos.ClientNotification?) { + errormsg(notification?.message.orEmpty()) + + _clientNotification.value = notification + } + + fun clearClientNotification() { + _clientNotification.value = null + } + private val _errorMessage = MutableStateFlow(null) val errorMessage: StateFlow get() = _errorMessage