@ -20,6 +20,7 @@ import io.element.android.libraries.androidutils.throttler.FirstThrottler
@@ -20,6 +20,7 @@ import io.element.android.libraries.androidutils.throttler.FirstThrottler
import io.element.android.libraries.core.cache.CircularCache
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
@ -41,6 +42,8 @@ import kotlinx.coroutines.withContext
@@ -41,6 +42,8 @@ import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
private val loggerTag = LoggerTag ( " DefaultNotificationDrawerManager " , LoggerTag . NotificationLoggerTag )
/ * *
* The NotificationDrawerManager receives notification events as they arrived ( from event stream or fcm ) and
* organise them in order to display them in the notification drawer .
@ -89,7 +92,11 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -89,7 +92,11 @@ class DefaultNotificationDrawerManager @Inject constructor(
is NavigationState . Space -> { }
is NavigationState . Room -> {
// Cleanup notification for current room
clearMessagesForRoom ( navigationState . parentSpace . parentSession . sessionId , navigationState . roomId )
clearMessagesForRoom (
sessionId = navigationState . parentSpace . parentSession . sessionId ,
roomId = navigationState . roomId ,
doRender = true ,
)
}
is NavigationState . Thread -> {
onEnteringThread (
@ -112,13 +119,13 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -112,13 +119,13 @@ class DefaultNotificationDrawerManager @Inject constructor(
private fun NotificationEventQueue . onNotifiableEventReceived ( notifiableEvent : NotifiableEvent ) {
if ( buildMeta . lowPrivacyLoggingEnabled ) {
Timber . d ( " onNotifiableEventReceived(): $notifiableEvent " )
Timber . tag ( loggerTag . value ) . d ( " onNotifiableEventReceived(): $notifiableEvent " )
} else {
Timber . d ( " onNotifiableEventReceived(): is push: ${notifiableEvent.canBeReplaced} " )
Timber . tag ( loggerTag . value ) . d ( " onNotifiableEventReceived(): is push: ${notifiableEvent.canBeReplaced} " )
}
if ( filteredEventDetector . shouldBeIgnored ( notifiableEvent ) ) {
Timber . d ( " onNotifiableEventReceived(): ignore the event " )
Timber . tag ( loggerTag . value ) . d ( " onNotifiableEventReceived(): ignore the event " )
return
}
@ -132,7 +139,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -132,7 +139,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
* Events might be grouped and there might not be one notification per event !
* /
fun onNotifiableEventReceived ( notifiableEvent : NotifiableEvent ) {
updateEvents {
updateEvents ( doRender = true ) {
it . onNotifiableEventReceived ( notifiableEvent )
}
}
@ -140,8 +147,8 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -140,8 +147,8 @@ class DefaultNotificationDrawerManager @Inject constructor(
/ * *
* Clear all known events and refresh the notification drawer .
* /
fun clearAllMessagesEvents ( sessionId : SessionId ) {
updateEvents {
fun clearAllMessagesEvents ( sessionId : SessionId , doRender : Boolean ) {
updateEvents ( doRender = doRender ) {
it . clearMessagesForSession ( sessionId )
}
}
@ -150,7 +157,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -150,7 +157,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
* Clear all notifications related to the session and refresh the notification drawer .
* /
fun clearAllEvents ( sessionId : SessionId ) {
updateEvents {
updateEvents ( doRender = true ) {
it . clearAllForSession ( sessionId )
}
}
@ -160,14 +167,14 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -160,14 +167,14 @@ class DefaultNotificationDrawerManager @Inject constructor(
* Used to ignore events related to that room ( no need to display notification ) and clean any existing notification on this room .
* Can also be called when a notification for this room is dismissed by the user .
* /
fun clearMessagesForRoom ( sessionId : SessionId , roomId : RoomId ) {
updateEvents {
fun clearMessagesForRoom ( sessionId : SessionId , roomId : RoomId , doRender : Boolean ) {
updateEvents ( doRender = doRender ) {
it . clearMessagesForRoom ( sessionId , roomId )
}
}
override fun clearMembershipNotificationForSession ( sessionId : SessionId ) {
updateEvents {
updateEvents ( doRender = true ) {
it . clearMembershipNotificationForSession ( sessionId )
}
}
@ -175,8 +182,12 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -175,8 +182,12 @@ class DefaultNotificationDrawerManager @Inject constructor(
/ * *
* Clear invitation notification for the provided room .
* /
override fun clearMembershipNotificationForRoom ( sessionId : SessionId , roomId : RoomId ) {
updateEvents {
override fun clearMembershipNotificationForRoom (
sessionId : SessionId ,
roomId : RoomId ,
doRender : Boolean ,
) {
updateEvents ( doRender = doRender ) {
it . clearMembershipNotificationForRoom ( sessionId , roomId )
}
}
@ -184,8 +195,8 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -184,8 +195,8 @@ class DefaultNotificationDrawerManager @Inject constructor(
/ * *
* Clear the notifications for a single event .
* /
fun clearEvent ( eventId : EventId ) {
updateEvents {
fun clearEvent ( eventId : EventId , doRender : Boolean ) {
updateEvents ( doRender = doRender ) {
it . clearEvent ( eventId )
}
}
@ -195,14 +206,14 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -195,14 +206,14 @@ class DefaultNotificationDrawerManager @Inject constructor(
* Used to ignore events related to that thread ( no need to display notification ) and clean any existing notification on this room .
* /
private fun onEnteringThread ( sessionId : SessionId , roomId : RoomId , threadId : ThreadId ) {
updateEvents {
updateEvents ( doRender = true ) {
it . clearMessagesForThread ( sessionId , roomId , threadId )
}
}
// TODO EAx Must be per account
fun notificationStyleChanged ( ) {
updateEvents {
updateEvents ( doRender = true ) {
val newSettings = true // pushDataStore.useCompleteNotificationFormat()
if ( newSettings != useCompleteNotificationFormat ) {
// Settings has changed, remove all current notifications
@ -212,41 +223,46 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -212,41 +223,46 @@ class DefaultNotificationDrawerManager @Inject constructor(
}
}
private fun updateEvents ( action : DefaultNotificationDrawerManager . ( NotificationEventQueue ) -> Unit ) {
notificationState . updateQueuedEvents ( this ) { queuedEvents , _ ->
private fun updateEvents (
doRender : Boolean ,
action : ( NotificationEventQueue ) -> Unit ,
) {
notificationState . updateQueuedEvents { queuedEvents , _ ->
action ( queuedEvents )
}
coroutineScope . refreshNotificationDrawer ( )
coroutineScope . refreshNotificationDrawer ( doRender )
}
private fun CoroutineScope . refreshNotificationDrawer ( ) = launch {
private fun CoroutineScope . refreshNotificationDrawer ( doRender : Boolean ) = launch {
// Implement last throttler
val canHandle = firstThrottler . canHandle ( )
Timber . v ( " refreshNotificationDrawer(), delay: ${canHandle.waitMillis()} ms " )
Timber . tag ( loggerTag . value ) . v ( " refreshNotificationDrawer( $doRender ), delay: ${canHandle.waitMillis()} ms " )
withContext ( dispatchers . io ) {
delay ( canHandle . waitMillis ( ) )
try {
refreshNotificationDrawerBg ( )
refreshNotificationDrawerBg ( doRender )
} catch ( throwable : Throwable ) {
// It can happen if for instance session has been destroyed. It's a bit ugly to try catch like this, but it's safer
Timber . w ( throwable , " refreshNotificationDrawerBg failure " )
Timber . tag ( loggerTag . value ) . w ( throwable , " refreshNotificationDrawerBg failure " )
}
}
}
private suspend fun refreshNotificationDrawerBg ( ) {
Timber . v ( " refreshNotificationDrawerBg() " )
val eventsToRender = notificationState . updateQueuedEvents ( this ) { queuedEvents , renderedEvents ->
private suspend fun refreshNotificationDrawerBg ( doRender : Boolean ) {
Timber . tag ( loggerTag . value ) . v ( " refreshNotificationDrawerBg( $doRender )" )
val eventsToRender = notificationState . updateQueuedEvents { queuedEvents , renderedEvents ->
notifiableEventProcessor . process ( queuedEvents . rawEvents ( ) , renderedEvents ) . also {
queuedEvents . clearAndAdd ( it . onlyKeptEvents ( ) )
}
}
if ( notificationState . hasAlreadyRendered ( eventsToRender ) ) {
Timber . d ( " Skipping notification update due to event list not changing " )
Timber . tag ( loggerTag . value ) . d ( " Skipping notification update due to event list not changing " )
} else {
notificationState . clearAndAddRenderedEvents ( eventsToRender )
renderEvents ( eventsToRender )
if ( doRender ) {
renderEvents ( eventsToRender )
}
persistEvents ( )
}
}
@ -265,7 +281,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
@@ -265,7 +281,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
eventsForSessions . forEach { ( sessionId , notifiableEvents ) ->
val currentUser = tryOrNull (
onError = { Timber . e ( it , " Unable to retrieve info for user ${sessionId.value} " ) } ,
onError = { Timber . tag ( loggerTag . value ) . e ( it , " Unable to retrieve info for user ${sessionId.value} " ) } ,
operation = {
val client = matrixClientProvider . getOrRestore ( sessionId ) . getOrThrow ( )
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash