fix(notifications): merge FG-service and message notifications into one
Use a single notification ID (9999) for both idle foreground-service state and incoming-message alerts. Shows sender name for 1 message, summary for multiple senders. Cancelling resets to idle instead of removing. Closes #111
This commit is contained in:
parent
b7ef6af0a4
commit
9eefa79c64
2 changed files with 105 additions and 106 deletions
|
|
@ -73,7 +73,7 @@ internal class MessagingService : Service() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val NOTIFICATION_ID = 9999
|
||||
internal const val NOTIFICATION_ID = 9999
|
||||
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, MessagingService::class.java)
|
||||
|
|
|
|||
|
|
@ -108,71 +108,38 @@ internal class NotificationHelper @Inject constructor(
|
|||
): Boolean {
|
||||
if (shouldSuppressNotification(senderId)) return false
|
||||
|
||||
val chatIntent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(EXTRA_RECIPIENT_ID, senderId)
|
||||
putExtra(EXTRA_RECIPIENT_USERNAME, senderUsername)
|
||||
}
|
||||
|
||||
val chatPendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
senderId.hashCode(),
|
||||
chatIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val messagesIntent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(EXTRA_OPEN_MESSAGES, true)
|
||||
}
|
||||
|
||||
val messagesPendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
SUMMARY_NOTIFICATION_ID,
|
||||
messagesIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_message)
|
||||
.setContentTitle("Neue Nachricht")
|
||||
.setContentText("$senderUsername hat etwas geschrieben")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(chatPendingIntent)
|
||||
.setGroup(GROUP_KEY)
|
||||
.build()
|
||||
|
||||
val senderNotificationId = senderId.hashCode()
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
try {
|
||||
val (shouldShowSummary, summaryText) = synchronized(activeSenderNotificationIds) {
|
||||
activeSenderNotificationIds.add(senderNotificationId)
|
||||
activeSenderNamesById[senderNotificationId] = senderUsername
|
||||
val (contentText, pendingIntent) = synchronized(activeSenderNotificationIds) {
|
||||
activeSenderNotificationIds.add(senderNotificationId)
|
||||
activeSenderNamesById[senderNotificationId] = senderUsername
|
||||
val names = activeSenderNamesById.values.toList()
|
||||
if (activeSenderNotificationIds.size == 1) {
|
||||
Pair(
|
||||
activeSenderNotificationIds.size > 1,
|
||||
buildSummaryText(activeSenderNamesById.values.toList())
|
||||
"Neue Nachricht von $senderUsername",
|
||||
buildChatPendingIntent(senderId, senderUsername)
|
||||
)
|
||||
} else {
|
||||
Pair(
|
||||
buildSummaryText(names),
|
||||
buildMessagesPendingIntent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val summaryNotification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_message)
|
||||
.setContentTitle("Neue Nachrichten")
|
||||
.setContentText(summaryText)
|
||||
.setGroup(GROUP_KEY)
|
||||
.setGroupSummary(true)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(messagesPendingIntent)
|
||||
.build()
|
||||
val messageNotification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_message)
|
||||
.setContentTitle("Bollwerk")
|
||||
.setContentText(contentText)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(false)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(senderNotificationId, notification)
|
||||
if (shouldShowSummary) {
|
||||
notificationManager.notify(SUMMARY_NOTIFICATION_ID, summaryNotification)
|
||||
} else {
|
||||
notificationManager.cancel(SUMMARY_NOTIFICATION_ID)
|
||||
}
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
try {
|
||||
notificationManager.notify(MessagingService.NOTIFICATION_ID, messageNotification)
|
||||
} catch (_: SecurityException) {
|
||||
// Permission not granted – ignore silently
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -188,66 +155,53 @@ internal class NotificationHelper @Inject constructor(
|
|||
}
|
||||
|
||||
/// Entfernt Benachrichtigungen für einen bestimmten Absender (wenn der Chat geöffnet wird).
|
||||
/// Wenn keine aktiven Absender mehr vorhanden sind, wird auf die Idle-Notification zurückgesetzt.
|
||||
fun cancelNotificationForSender(senderId: String) {
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
val senderNotificationId = senderId.hashCode()
|
||||
notificationManager.cancel(senderNotificationId)
|
||||
val hasMoreSenders = synchronized(activeSenderNotificationIds) {
|
||||
val remainingText: String? = synchronized(activeSenderNotificationIds) {
|
||||
activeSenderNotificationIds.remove(senderNotificationId)
|
||||
activeSenderNamesById.remove(senderNotificationId)
|
||||
activeSenderNotificationIds.isNotEmpty()
|
||||
}
|
||||
if (!hasMoreSenders) {
|
||||
notificationManager.cancel(SUMMARY_NOTIFICATION_ID)
|
||||
if (activeSenderNotificationIds.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
buildSummaryText(activeSenderNamesById.values.toList())
|
||||
}
|
||||
}
|
||||
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
try {
|
||||
if (remainingText == null) {
|
||||
notificationManager.notify(MessagingService.NOTIFICATION_ID, buildIdleNotification())
|
||||
} else {
|
||||
val updatedNotification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_message)
|
||||
.setContentTitle("Bollwerk")
|
||||
.setContentText(remainingText)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(false)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(buildMessagesPendingIntent())
|
||||
.build()
|
||||
notificationManager.notify(MessagingService.NOTIFICATION_ID, updatedNotification)
|
||||
}
|
||||
} catch (_: SecurityException) {}
|
||||
}
|
||||
|
||||
/// Aktualisiert den Launcher-Badge-Zähler für ungelesene Nachrichten.
|
||||
fun updateBadgeCount(count: Int) {
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
if (count <= 0) {
|
||||
notificationManager.cancel(SUMMARY_NOTIFICATION_ID)
|
||||
return
|
||||
}
|
||||
// If per-sender notifications are active, the summary notification is already informative.
|
||||
val hasSenderNotifications = synchronized(activeSenderNotificationIds) {
|
||||
activeSenderNotificationIds.isNotEmpty()
|
||||
}
|
||||
if (hasSenderNotifications) return
|
||||
val messagesIntent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(EXTRA_OPEN_MESSAGES, true)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context, SUMMARY_NOTIFICATION_ID, messagesIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val badgeNotification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_message)
|
||||
.setContentTitle("Ungelesene Nachrichten")
|
||||
.setContentText("$count ungelesene Nachrichten")
|
||||
.setNumber(count)
|
||||
.setGroup(GROUP_KEY)
|
||||
.setGroupSummary(true)
|
||||
.setAutoCancel(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build()
|
||||
try {
|
||||
notificationManager.notify(SUMMARY_NOTIFICATION_ID, badgeNotification)
|
||||
} catch (_: SecurityException) {}
|
||||
// Badge-Logik ist mit der vereinheitlichten Notification redundant.
|
||||
// Kein separater Badge-Notification nötig, da die FG-Notification immer sichtbar ist.
|
||||
}
|
||||
|
||||
fun cancelAllMessageNotifications() {
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
val ids = synchronized(activeSenderNotificationIds) {
|
||||
val copy = activeSenderNotificationIds.toList()
|
||||
synchronized(activeSenderNotificationIds) {
|
||||
activeSenderNotificationIds.clear()
|
||||
activeSenderNamesById.clear()
|
||||
copy
|
||||
}
|
||||
ids.forEach { notificationManager.cancel(it) }
|
||||
notificationManager.cancel(SUMMARY_NOTIFICATION_ID)
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
try {
|
||||
notificationManager.notify(MessagingService.NOTIFICATION_ID, buildIdleNotification())
|
||||
} catch (_: SecurityException) {}
|
||||
}
|
||||
|
||||
private fun buildSummaryText(senderNames: List<String>): String {
|
||||
|
|
@ -263,13 +217,58 @@ internal class NotificationHelper @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildIdleNotification(): android.app.Notification {
|
||||
val openAppIntent = PendingIntent.getActivity(
|
||||
context, 0,
|
||||
Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
return NotificationCompat.Builder(context, SERVICE_CHANNEL_ID)
|
||||
.setContentTitle("Bollwerk")
|
||||
.setContentText("Warten auf Nachrichten…")
|
||||
.setSmallIcon(R.drawable.ic_notification_message)
|
||||
.setOngoing(true)
|
||||
.setSilent(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setContentIntent(openAppIntent)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun buildChatPendingIntent(senderId: String, senderUsername: String): PendingIntent {
|
||||
val chatIntent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(EXTRA_RECIPIENT_ID, senderId)
|
||||
putExtra(EXTRA_RECIPIENT_USERNAME, senderUsername)
|
||||
}
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
senderId.hashCode(),
|
||||
chatIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildMessagesPendingIntent(): PendingIntent {
|
||||
val messagesIntent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(EXTRA_OPEN_MESSAGES, true)
|
||||
}
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
MESSAGES_REQUEST_CODE,
|
||||
messagesIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "bollwerk_messages"
|
||||
private const val CHANNEL_NAME = "Chat-Nachrichten"
|
||||
const val SERVICE_CHANNEL_ID = "bollwerk_service"
|
||||
private const val SERVICE_CHANNEL_NAME = "Nachrichtendienst"
|
||||
private const val GROUP_KEY = "de.bollwerk.app.MESSAGES"
|
||||
private const val SUMMARY_NOTIFICATION_ID = 0
|
||||
private const val MESSAGES_REQUEST_CODE = 1001
|
||||
|
||||
const val EXTRA_RECIPIENT_ID = "notification_recipient_id"
|
||||
const val EXTRA_RECIPIENT_USERNAME = "notification_recipient_username"
|
||||
|
|
|
|||
Loading…
Reference in a new issue