refactor: Client Notification Support (#2095)

This commit is contained in:
James Rich 2025-06-13 01:00:28 +00:00 committed by GitHub
parent 2a274e259b
commit 606d1520d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 101 additions and 1 deletions

View file

@ -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)

View file

@ -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<NodeEntity>) {
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<String?> get() = serviceRepository.errorMessage
fun setErrorMessage(text: String) {

View file

@ -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)
}

View file

@ -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()
}
}

View file

@ -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<MeshProtos.ClientNotification?>(null)
val clientNotification: StateFlow<MeshProtos.ClientNotification?> get() = _clientNotification
fun setClientNotification(notification: MeshProtos.ClientNotification?) {
errormsg(notification?.message.orEmpty())
_clientNotification.value = notification
}
fun clearClientNotification() {
_clientNotification.value = null
}
private val _errorMessage = MutableStateFlow<String?>(null)
val errorMessage: StateFlow<String?> get() = _errorMessage