diff --git a/app/src/main/java/de/bollwerk/app/notification/MessagingService.kt b/app/src/main/java/de/bollwerk/app/notification/MessagingService.kt index 489a247..5f09987 100644 --- a/app/src/main/java/de/bollwerk/app/notification/MessagingService.kt +++ b/app/src/main/java/de/bollwerk/app/notification/MessagingService.kt @@ -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) diff --git a/app/src/main/java/de/bollwerk/app/notification/NotificationHelper.kt b/app/src/main/java/de/bollwerk/app/notification/NotificationHelper.kt index 9d03843..672675a 100644 --- a/app/src/main/java/de/bollwerk/app/notification/NotificationHelper.kt +++ b/app/src/main/java/de/bollwerk/app/notification/NotificationHelper.kt @@ -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 { @@ -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"