Merge pull request #458 from vector-im/feature/bma/notificationContent
Notification content
This commit is contained in:
commit
be45feac43
19 changed files with 387 additions and 224 deletions
|
|
@ -20,15 +20,23 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
||||||
//TODO add content
|
|
||||||
data class NotificationData(
|
data class NotificationData(
|
||||||
val senderId: UserId,
|
val senderId: UserId,
|
||||||
val eventId: EventId,
|
val eventId: EventId,
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val senderAvatarUrl: String? = null,
|
val senderAvatarUrl: String?,
|
||||||
val senderDisplayName: String? = null,
|
val senderDisplayName: String?,
|
||||||
val roomAvatarUrl: String? = null,
|
val roomAvatarUrl: String?,
|
||||||
|
val roomDisplayName: String?,
|
||||||
val isDirect: Boolean,
|
val isDirect: Boolean,
|
||||||
val isEncrypted: Boolean,
|
val isEncrypted: Boolean,
|
||||||
val isNoisy: Boolean,
|
val isNoisy: Boolean,
|
||||||
|
val event: NotificationEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class NotificationEvent(
|
||||||
|
val timestamp: Long,
|
||||||
|
val content: String,
|
||||||
|
// For images for instance
|
||||||
|
val contentUrl: String?
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||||
import org.matrix.rustcomponents.sdk.NotificationItem
|
import org.matrix.rustcomponents.sdk.NotificationItem
|
||||||
import org.matrix.rustcomponents.sdk.use
|
import org.matrix.rustcomponents.sdk.use
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class NotificationMapper @Inject constructor() {
|
class NotificationMapper {
|
||||||
|
private val timelineEventMapper = TimelineEventMapper()
|
||||||
|
|
||||||
fun map(notificationItem: NotificationItem): NotificationData {
|
fun map(notificationItem: NotificationItem): NotificationData {
|
||||||
return notificationItem.use {
|
return notificationItem.use {
|
||||||
|
|
@ -36,9 +36,11 @@ class NotificationMapper @Inject constructor() {
|
||||||
senderAvatarUrl = it.senderAvatarUrl,
|
senderAvatarUrl = it.senderAvatarUrl,
|
||||||
senderDisplayName = it.senderDisplayName,
|
senderDisplayName = it.senderDisplayName,
|
||||||
roomAvatarUrl = it.roomAvatarUrl,
|
roomAvatarUrl = it.roomAvatarUrl,
|
||||||
|
roomDisplayName = it.roomDisplayName,
|
||||||
isDirect = it.isDirect,
|
isDirect = it.isDirect,
|
||||||
isEncrypted = it.isEncrypted.orFalse(),
|
isEncrypted = it.isEncrypted.orFalse(),
|
||||||
isNoisy = it.isNoisy
|
isNoisy = it.isNoisy,
|
||||||
|
event = it.event.use { event -> timelineEventMapper.map(event) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,13 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.notification
|
package io.element.android.libraries.matrix.impl.notification
|
||||||
|
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.matrix.rustcomponents.sdk.Client
|
import org.matrix.rustcomponents.sdk.Client
|
||||||
import org.matrix.rustcomponents.sdk.use
|
import org.matrix.rustcomponents.sdk.use
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class RustNotificationService(
|
class RustNotificationService(
|
||||||
private val client: Client,
|
private val client: Client,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.notification
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.notification.NotificationEvent
|
||||||
|
import org.matrix.rustcomponents.sdk.MessageLikeEventContent
|
||||||
|
import org.matrix.rustcomponents.sdk.MessageType
|
||||||
|
import org.matrix.rustcomponents.sdk.StateEventContent
|
||||||
|
import org.matrix.rustcomponents.sdk.TimelineEvent
|
||||||
|
import org.matrix.rustcomponents.sdk.TimelineEventType
|
||||||
|
import org.matrix.rustcomponents.sdk.use
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TimelineEventMapper @Inject constructor() {
|
||||||
|
|
||||||
|
fun map(timelineEvent: TimelineEvent): NotificationEvent {
|
||||||
|
return timelineEvent.use {
|
||||||
|
NotificationEvent(
|
||||||
|
timestamp = it.timestamp().toLong(),
|
||||||
|
content = it.eventType().toContent(),
|
||||||
|
contentUrl = null // TODO it.eventType().toContentUrl(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TimelineEventType.toContent(): String {
|
||||||
|
return when (this) {
|
||||||
|
is TimelineEventType.MessageLike -> content.toContent()
|
||||||
|
is TimelineEventType.State -> content.toContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StateEventContent.toContent(): String {
|
||||||
|
return when (this) {
|
||||||
|
StateEventContent.PolicyRuleRoom -> "PolicyRuleRoom"
|
||||||
|
StateEventContent.PolicyRuleServer -> "PolicyRuleServer"
|
||||||
|
StateEventContent.PolicyRuleUser -> "PolicyRuleUser"
|
||||||
|
StateEventContent.RoomAliases -> "RoomAliases"
|
||||||
|
StateEventContent.RoomAvatar -> "RoomAvatar"
|
||||||
|
StateEventContent.RoomCanonicalAlias -> "RoomCanonicalAlias"
|
||||||
|
StateEventContent.RoomCreate -> "RoomCreate"
|
||||||
|
StateEventContent.RoomEncryption -> "RoomEncryption"
|
||||||
|
StateEventContent.RoomGuestAccess -> "RoomGuestAccess"
|
||||||
|
StateEventContent.RoomHistoryVisibility -> "RoomHistoryVisibility"
|
||||||
|
StateEventContent.RoomJoinRules -> "RoomJoinRules"
|
||||||
|
is StateEventContent.RoomMemberContent -> "$userId is now $membershipState"
|
||||||
|
StateEventContent.RoomName -> "RoomName"
|
||||||
|
StateEventContent.RoomPinnedEvents -> "RoomPinnedEvents"
|
||||||
|
StateEventContent.RoomPowerLevels -> "RoomPowerLevels"
|
||||||
|
StateEventContent.RoomServerAcl -> "RoomServerAcl"
|
||||||
|
StateEventContent.RoomThirdPartyInvite -> "RoomThirdPartyInvite"
|
||||||
|
StateEventContent.RoomTombstone -> "RoomTombstone"
|
||||||
|
StateEventContent.RoomTopic -> "RoomTopic"
|
||||||
|
StateEventContent.SpaceChild -> "SpaceChild"
|
||||||
|
StateEventContent.SpaceParent -> "SpaceParent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MessageLikeEventContent.toContent(): String {
|
||||||
|
return use {
|
||||||
|
when (it) {
|
||||||
|
MessageLikeEventContent.CallAnswer -> "CallAnswer"
|
||||||
|
MessageLikeEventContent.CallCandidates -> "CallCandidates"
|
||||||
|
MessageLikeEventContent.CallHangup -> "CallHangup"
|
||||||
|
MessageLikeEventContent.CallInvite -> "CallInvite"
|
||||||
|
MessageLikeEventContent.KeyVerificationAccept -> "KeyVerificationAccept"
|
||||||
|
MessageLikeEventContent.KeyVerificationCancel -> "KeyVerificationCancel"
|
||||||
|
MessageLikeEventContent.KeyVerificationDone -> "KeyVerificationDone"
|
||||||
|
MessageLikeEventContent.KeyVerificationKey -> "KeyVerificationKey"
|
||||||
|
MessageLikeEventContent.KeyVerificationMac -> "KeyVerificationMac"
|
||||||
|
MessageLikeEventContent.KeyVerificationReady -> "KeyVerificationReady"
|
||||||
|
MessageLikeEventContent.KeyVerificationStart -> "KeyVerificationStart"
|
||||||
|
is MessageLikeEventContent.ReactionContent -> "Reacted to ${it.relatedEventId.take(8)}…"
|
||||||
|
MessageLikeEventContent.RoomEncrypted -> "RoomEncrypted"
|
||||||
|
is MessageLikeEventContent.RoomMessage -> it.messageType.toContent()
|
||||||
|
MessageLikeEventContent.RoomRedaction -> "RoomRedaction"
|
||||||
|
MessageLikeEventContent.Sticker -> "Sticker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MessageType.toContent(): String {
|
||||||
|
return when (this) {
|
||||||
|
is MessageType.Audio -> content.use { it.body }
|
||||||
|
is MessageType.Emote -> content.body
|
||||||
|
is MessageType.File -> content.use { it.body }
|
||||||
|
is MessageType.Image -> content.use { it.body }
|
||||||
|
is MessageType.Notice -> content.body
|
||||||
|
is MessageType.Text -> content.body
|
||||||
|
is MessageType.Video -> content.use { it.body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ dependencies {
|
||||||
implementation(libs.androidx.security.crypto)
|
implementation(libs.androidx.security.crypto)
|
||||||
implementation(libs.network.retrofit)
|
implementation(libs.network.retrofit)
|
||||||
implementation(libs.serialization.json)
|
implementation(libs.serialization.json)
|
||||||
|
implementation(libs.coil)
|
||||||
|
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
@ -42,6 +43,7 @@ dependencies {
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.network)
|
implementation(projects.libraries.network)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.matrixui)
|
||||||
api(projects.libraries.pushproviders.api)
|
api(projects.libraries.pushproviders.api)
|
||||||
api(projects.libraries.pushstore.api)
|
api(projects.libraries.pushstore.api)
|
||||||
api(projects.libraries.push.api)
|
api(projects.libraries.push.api)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||||
|
import io.element.android.libraries.matrix.api.notification.NotificationEvent
|
||||||
import io.element.android.libraries.push.impl.log.pushLoggerTag
|
import io.element.android.libraries.push.impl.log.pushLoggerTag
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
|
|
@ -44,9 +45,9 @@ class NotifiableEventResolver @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
// private val noticeEventFormatter: NoticeEventFormatter,
|
// private val noticeEventFormatter: NoticeEventFormatter,
|
||||||
// private val displayableEventFormatter: DisplayableEventFormatter,
|
// private val displayableEventFormatter: DisplayableEventFormatter,
|
||||||
private val clock: SystemClock,
|
|
||||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val clock: SystemClock,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||||
|
|
@ -80,14 +81,14 @@ class NotifiableEventResolver @Inject constructor(
|
||||||
editedEventId = null,
|
editedEventId = null,
|
||||||
canBeReplaced = true,
|
canBeReplaced = true,
|
||||||
noisy = isNoisy,
|
noisy = isNoisy,
|
||||||
timestamp = clock.epochMillis(),
|
timestamp = event.timestamp,
|
||||||
senderName = senderDisplayName,
|
senderName = senderDisplayName,
|
||||||
senderId = senderId.value,
|
senderId = senderId.value,
|
||||||
body = "Message ${eventId.value.take(8)}… in room ${roomId.value.take(8)}…",
|
body = event.content,
|
||||||
imageUriString = null,
|
imageUriString = event.contentUrl,
|
||||||
threadId = null,
|
threadId = null,
|
||||||
roomName = null,
|
roomName = roomDisplayName,
|
||||||
roomIsDirect = false,
|
roomIsDirect = isDirect,
|
||||||
roomAvatarPath = roomAvatarUrl,
|
roomAvatarPath = roomAvatarUrl,
|
||||||
senderAvatarPath = senderAvatarUrl,
|
senderAvatarPath = senderAvatarUrl,
|
||||||
soundName = null,
|
soundName = null,
|
||||||
|
|
@ -97,18 +98,27 @@ class NotifiableEventResolver @Inject constructor(
|
||||||
isUpdated = false
|
isUpdated = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO This is a temporary method for EAx.
|
* TODO This is a temporary method for EAx.
|
||||||
*/
|
*/
|
||||||
private fun NotificationData?.orDefault(roomId: RoomId, eventId: EventId): NotificationData {
|
private fun NotificationData?.orDefault(roomId: RoomId, eventId: EventId): NotificationData {
|
||||||
return this ?: NotificationData(
|
return this ?: NotificationData(
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
senderId = UserId("@user:domain"),
|
senderId = UserId("@user:domain"),
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
isNoisy = false,
|
senderAvatarUrl = null,
|
||||||
isEncrypted = false,
|
senderDisplayName = null,
|
||||||
isDirect = false
|
roomAvatarUrl = null,
|
||||||
)
|
roomDisplayName = null,
|
||||||
|
isNoisy = false,
|
||||||
|
isEncrypted = false,
|
||||||
|
isDirect = false,
|
||||||
|
event = NotificationEvent(
|
||||||
|
timestamp = clock.epochMillis(),
|
||||||
|
content = "Message ${eventId.value.take(8)}… in room ${roomId.value.take(8)}…",
|
||||||
|
contentUrl = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,14 @@ package io.element.android.libraries.push.impl.notifications
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.WorkerThread
|
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import coil.imageLoader
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||||
|
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -31,30 +36,24 @@ class NotificationBitmapLoader @Inject constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get icon of a room.
|
* Get icon of a room.
|
||||||
|
* @param path mxc url
|
||||||
*/
|
*/
|
||||||
@WorkerThread
|
suspend fun getRoomBitmap(path: String?): Bitmap? {
|
||||||
fun getRoomBitmap(path: String?): Bitmap? {
|
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return loadRoomBitmap(path)
|
return loadRoomBitmap(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
private suspend fun loadRoomBitmap(path: String): Bitmap? {
|
||||||
private fun loadRoomBitmap(path: String): Bitmap? {
|
|
||||||
return try {
|
return try {
|
||||||
null
|
val imageRequest = ImageRequest.Builder(context)
|
||||||
/* TODO Notification
|
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024)))
|
||||||
Glide.with(context)
|
.build()
|
||||||
.asBitmap()
|
val result = context.imageLoader.execute(imageRequest)
|
||||||
.load(path)
|
result.drawable?.toBitmap()
|
||||||
.format(DecodeFormat.PREFER_ARGB_8888)
|
} catch (e: Throwable) {
|
||||||
.signature(ObjectKey("room-icon-notification"))
|
Timber.e(e, "Unable to load room bitmap")
|
||||||
.submit()
|
|
||||||
.get()
|
|
||||||
*/
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "decodeFile failed")
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,9 +61,9 @@ class NotificationBitmapLoader @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Get icon of a user.
|
* Get icon of a user.
|
||||||
* Before Android P, this does nothing because the icon won't be used
|
* Before Android P, this does nothing because the icon won't be used
|
||||||
|
* @param path mxc url
|
||||||
*/
|
*/
|
||||||
@WorkerThread
|
suspend fun getUserIcon(path: String?): IconCompat? {
|
||||||
fun getUserIcon(path: String?): IconCompat? {
|
|
||||||
if (path == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
if (path == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
@ -72,23 +71,17 @@ class NotificationBitmapLoader @Inject constructor(
|
||||||
return loadUserIcon(path)
|
return loadUserIcon(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
private suspend fun loadUserIcon(path: String): IconCompat? {
|
||||||
private fun loadUserIcon(path: String): IconCompat? {
|
|
||||||
return try {
|
return try {
|
||||||
null
|
val imageRequest = ImageRequest.Builder(context)
|
||||||
/* TODO Notification
|
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024)))
|
||||||
val bitmap = Glide.with(context)
|
.transformations(CircleCropTransformation())
|
||||||
.asBitmap()
|
.build()
|
||||||
.load(path)
|
val result = context.imageLoader.execute(imageRequest)
|
||||||
.transform(CircleCrop())
|
val bitmap = result.drawable?.toBitmap()
|
||||||
.format(DecodeFormat.PREFER_ARGB_8888)
|
return bitmap?.let { IconCompat.createWithBitmap(it) }
|
||||||
.signature(ObjectKey("user-icon-notification"))
|
} catch (e: Throwable) {
|
||||||
.submit()
|
Timber.e(e, "Unable to load user bitmap")
|
||||||
.get()
|
|
||||||
IconCompat.createWithBitmap(bitmap)
|
|
||||||
*/
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "decodeFile failed")
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,28 +16,28 @@
|
||||||
|
|
||||||
package io.element.android.libraries.push.impl.notifications
|
package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import androidx.annotation.WorkerThread
|
|
||||||
import io.element.android.libraries.androidutils.throttler.FirstThrottler
|
import io.element.android.libraries.androidutils.throttler.FirstThrottler
|
||||||
import io.element.android.libraries.core.cache.CircularCache
|
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.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.push.api.store.PushDataStore
|
import io.element.android.libraries.push.api.store.PushDataStore
|
||||||
import io.element.android.libraries.push.impl.R
|
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.shouldIgnoreMessageEventInRoom
|
import io.element.android.libraries.push.impl.notifications.model.shouldIgnoreMessageEventInRoom
|
||||||
import io.element.android.services.appnavstate.api.AppNavigationState
|
import io.element.android.services.appnavstate.api.AppNavigationState
|
||||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -48,7 +48,6 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
class NotificationDrawerManager @Inject constructor(
|
class NotificationDrawerManager @Inject constructor(
|
||||||
@ApplicationContext context: Context,
|
|
||||||
private val pushDataStore: PushDataStore,
|
private val pushDataStore: PushDataStore,
|
||||||
private val notifiableEventProcessor: NotifiableEventProcessor,
|
private val notifiableEventProcessor: NotifiableEventProcessor,
|
||||||
private val notificationRenderer: NotificationRenderer,
|
private val notificationRenderer: NotificationRenderer,
|
||||||
|
|
@ -56,17 +55,14 @@ class NotificationDrawerManager @Inject constructor(
|
||||||
private val filteredEventDetector: FilteredEventDetector,
|
private val filteredEventDetector: FilteredEventDetector,
|
||||||
private val appNavigationStateService: AppNavigationStateService,
|
private val appNavigationStateService: AppNavigationStateService,
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY)
|
|
||||||
private var backgroundHandler: Handler
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazily initializes the NotificationState as we rely on having a current session in order to fetch the persisted queue of events.
|
* Lazily initializes the NotificationState as we rely on having a current session in order to fetch the persisted queue of events.
|
||||||
*/
|
*/
|
||||||
private val notificationState by lazy { createInitialNotificationState() }
|
private val notificationState by lazy { createInitialNotificationState() }
|
||||||
private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
|
|
||||||
private var currentAppNavigationState: AppNavigationState? = null
|
private var currentAppNavigationState: AppNavigationState? = null
|
||||||
private val firstThrottler = FirstThrottler(200)
|
private val firstThrottler = FirstThrottler(200)
|
||||||
|
|
||||||
|
|
@ -74,8 +70,6 @@ class NotificationDrawerManager @Inject constructor(
|
||||||
private var useCompleteNotificationFormat = true
|
private var useCompleteNotificationFormat = true
|
||||||
|
|
||||||
init {
|
init {
|
||||||
handlerThread.start()
|
|
||||||
backgroundHandler = Handler(handlerThread.looper)
|
|
||||||
// Observe application state
|
// Observe application state
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
appNavigationStateService.appNavigationStateFlow
|
appNavigationStateService.appNavigationStateFlow
|
||||||
|
|
@ -193,30 +187,25 @@ class NotificationDrawerManager @Inject constructor(
|
||||||
notificationState.updateQueuedEvents(this) { queuedEvents, _ ->
|
notificationState.updateQueuedEvents(this) { queuedEvents, _ ->
|
||||||
action(queuedEvents)
|
action(queuedEvents)
|
||||||
}
|
}
|
||||||
refreshNotificationDrawer()
|
coroutineScope.refreshNotificationDrawer()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshNotificationDrawer() {
|
private fun CoroutineScope.refreshNotificationDrawer() = launch {
|
||||||
// Implement last throttler
|
// Implement last throttler
|
||||||
val canHandle = firstThrottler.canHandle()
|
val canHandle = firstThrottler.canHandle()
|
||||||
Timber.v("refreshNotificationDrawer(), delay: ${canHandle.waitMillis()} ms")
|
Timber.v("refreshNotificationDrawer(), delay: ${canHandle.waitMillis()} ms")
|
||||||
backgroundHandler.removeCallbacksAndMessages(null)
|
withContext(dispatchers.io) {
|
||||||
|
delay(canHandle.waitMillis())
|
||||||
backgroundHandler.postDelayed(
|
try {
|
||||||
{
|
refreshNotificationDrawerBg()
|
||||||
try {
|
} catch (throwable: Throwable) {
|
||||||
refreshNotificationDrawerBg()
|
// It can happen if for instance session has been destroyed. It's a bit ugly to try catch like this, but it's safer
|
||||||
} catch (throwable: Throwable) {
|
Timber.w(throwable, "refreshNotificationDrawerBg failure")
|
||||||
// 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")
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
canHandle.waitMillis()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
private suspend fun refreshNotificationDrawerBg() {
|
||||||
private fun refreshNotificationDrawerBg() {
|
|
||||||
Timber.v("refreshNotificationDrawerBg()")
|
Timber.v("refreshNotificationDrawerBg()")
|
||||||
val eventsToRender = notificationState.updateQueuedEvents(this) { queuedEvents, renderedEvents ->
|
val eventsToRender = notificationState.updateQueuedEvents(this) { queuedEvents, renderedEvents ->
|
||||||
notifiableEventProcessor.process(queuedEvents.rawEvents(), currentAppNavigationState, renderedEvents).also {
|
notifiableEventProcessor.process(queuedEvents.rawEvents(), currentAppNavigationState, renderedEvents).also {
|
||||||
|
|
@ -239,24 +228,34 @@ class NotificationDrawerManager @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderEvents(eventsToRender: List<ProcessedEvent<NotifiableEvent>>) {
|
private suspend fun renderEvents(eventsToRender: List<ProcessedEvent<NotifiableEvent>>) {
|
||||||
// Group by sessionId
|
// Group by sessionId
|
||||||
val eventsForSessions = eventsToRender.groupBy {
|
val eventsForSessions = eventsToRender.groupBy {
|
||||||
it.event.sessionId
|
it.event.sessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
eventsForSessions.forEach { (sessionId, notifiableEvents) ->
|
eventsForSessions.forEach { (sessionId, notifiableEvents) ->
|
||||||
// TODO EAx val user = session.getUserOrDefault(session.myUserId)
|
val currentUser = tryOrNull(
|
||||||
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
|
onError = { Timber.e(it, "Unable to retrieve info for user ${sessionId.value}") },
|
||||||
val myUserDisplayName = "Todo display name" // user.toMatrixItem().getBestName()
|
operation = {
|
||||||
// TODO EAx avatar URL
|
val client = matrixAuthenticationService.restoreSession(sessionId).getOrNull()
|
||||||
val myUserAvatarUrl = null // session.contentUrlResolver().resolveThumbnail(
|
|
||||||
// contentUrl = user.avatarUrl,
|
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
|
||||||
// width = avatarSize,
|
val myUserDisplayName = client?.loadUserDisplayName()?.getOrNull() ?: sessionId.value
|
||||||
// height = avatarSize,
|
val userAvatarUrl = client?.loadUserAvatarURLString()?.getOrNull()
|
||||||
// method = ContentUrlResolver.ThumbnailMethod.SCALE
|
MatrixUser(
|
||||||
//)
|
userId = sessionId,
|
||||||
notificationRenderer.render(sessionId, myUserDisplayName, myUserAvatarUrl, useCompleteNotificationFormat, notifiableEvents)
|
displayName = myUserDisplayName,
|
||||||
|
avatarUrl = userAvatarUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) ?: MatrixUser(
|
||||||
|
userId = sessionId,
|
||||||
|
displayName = sessionId.value,
|
||||||
|
avatarUrl = null
|
||||||
|
)
|
||||||
|
|
||||||
|
notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory
|
import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
|
|
@ -34,10 +34,8 @@ class NotificationFactory @Inject constructor(
|
||||||
private val summaryGroupMessageCreator: SummaryGroupMessageCreator
|
private val summaryGroupMessageCreator: SummaryGroupMessageCreator
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun Map<RoomId, ProcessedMessageEvents>.toNotifications(
|
suspend fun Map<RoomId, ProcessedMessageEvents>.toNotifications(
|
||||||
sessionId: SessionId,
|
currentUser: MatrixUser,
|
||||||
myUserDisplayName: String,
|
|
||||||
myUserAvatarUrl: String?
|
|
||||||
): List<RoomNotification> {
|
): List<RoomNotification> {
|
||||||
return map { (roomId, events) ->
|
return map { (roomId, events) ->
|
||||||
when {
|
when {
|
||||||
|
|
@ -45,11 +43,9 @@ class NotificationFactory @Inject constructor(
|
||||||
else -> {
|
else -> {
|
||||||
val messageEvents = events.onlyKeptEvents().filterNot { it.isRedacted }
|
val messageEvents = events.onlyKeptEvents().filterNot { it.isRedacted }
|
||||||
roomGroupMessageCreator.createRoomMessage(
|
roomGroupMessageCreator.createRoomMessage(
|
||||||
sessionId = sessionId,
|
currentUser = currentUser,
|
||||||
events = messageEvents,
|
events = messageEvents,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
userDisplayName = myUserDisplayName,
|
|
||||||
userAvatarUrl = myUserAvatarUrl
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +95,7 @@ class NotificationFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSummaryNotification(
|
fun createSummaryNotification(
|
||||||
sessionId: SessionId,
|
currentUser: MatrixUser,
|
||||||
roomNotifications: List<RoomNotification>,
|
roomNotifications: List<RoomNotification>,
|
||||||
invitationNotifications: List<OneShotNotification>,
|
invitationNotifications: List<OneShotNotification>,
|
||||||
simpleNotifications: List<OneShotNotification>,
|
simpleNotifications: List<OneShotNotification>,
|
||||||
|
|
@ -112,7 +108,7 @@ class NotificationFactory @Inject constructor(
|
||||||
roomMeta.isEmpty() && invitationMeta.isEmpty() && simpleMeta.isEmpty() -> SummaryNotification.Removed
|
roomMeta.isEmpty() && invitationMeta.isEmpty() && simpleMeta.isEmpty() -> SummaryNotification.Removed
|
||||||
else -> SummaryNotification.Update(
|
else -> SummaryNotification.Update(
|
||||||
summaryGroupMessageCreator.createSummaryNotification(
|
summaryGroupMessageCreator.createSummaryNotification(
|
||||||
sessionId = sessionId,
|
currentUser = currentUser,
|
||||||
roomNotifications = roomMeta,
|
roomNotifications = roomMeta,
|
||||||
invitationNotifications = invitationMeta,
|
invitationNotifications = invitationMeta,
|
||||||
simpleNotifications = simpleMeta,
|
simpleNotifications = simpleMeta,
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,8 @@
|
||||||
|
|
||||||
package io.element.android.libraries.push.impl.notifications
|
package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
|
|
@ -32,21 +31,18 @@ class NotificationRenderer @Inject constructor(
|
||||||
private val notificationFactory: NotificationFactory,
|
private val notificationFactory: NotificationFactory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@WorkerThread
|
suspend fun render(
|
||||||
fun render(
|
currentUser: MatrixUser,
|
||||||
sessionId: SessionId,
|
|
||||||
myUserDisplayName: String,
|
|
||||||
myUserAvatarUrl: String?,
|
|
||||||
useCompleteNotificationFormat: Boolean,
|
useCompleteNotificationFormat: Boolean,
|
||||||
eventsToProcess: List<ProcessedEvent<NotifiableEvent>>
|
eventsToProcess: List<ProcessedEvent<NotifiableEvent>>
|
||||||
) {
|
) {
|
||||||
val (roomEvents, simpleEvents, invitationEvents) = eventsToProcess.groupByType()
|
val (roomEvents, simpleEvents, invitationEvents) = eventsToProcess.groupByType()
|
||||||
with(notificationFactory) {
|
with(notificationFactory) {
|
||||||
val roomNotifications = roomEvents.toNotifications(sessionId, myUserDisplayName, myUserAvatarUrl)
|
val roomNotifications = roomEvents.toNotifications(currentUser)
|
||||||
val invitationNotifications = invitationEvents.toNotifications()
|
val invitationNotifications = invitationEvents.toNotifications()
|
||||||
val simpleNotifications = simpleEvents.toNotifications()
|
val simpleNotifications = simpleEvents.toNotifications()
|
||||||
val summaryNotification = createSummaryNotification(
|
val summaryNotification = createSummaryNotification(
|
||||||
sessionId = sessionId,
|
currentUser = currentUser,
|
||||||
roomNotifications = roomNotifications,
|
roomNotifications = roomNotifications,
|
||||||
invitationNotifications = invitationNotifications,
|
invitationNotifications = invitationNotifications,
|
||||||
simpleNotifications = simpleNotifications,
|
simpleNotifications = simpleNotifications,
|
||||||
|
|
@ -56,21 +52,27 @@ class NotificationRenderer @Inject constructor(
|
||||||
// Remove summary first to avoid briefly displaying it after dismissing the last notification
|
// Remove summary first to avoid briefly displaying it after dismissing the last notification
|
||||||
if (summaryNotification == SummaryNotification.Removed) {
|
if (summaryNotification == SummaryNotification.Removed) {
|
||||||
Timber.d("Removing summary notification")
|
Timber.d("Removing summary notification")
|
||||||
notificationDisplayer.cancelNotificationMessage(null, notificationIdProvider.getSummaryNotificationId(sessionId))
|
notificationDisplayer.cancelNotificationMessage(
|
||||||
|
tag = null,
|
||||||
|
id = notificationIdProvider.getSummaryNotificationId(currentUser.userId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
roomNotifications.forEach { wrapper ->
|
roomNotifications.forEach { wrapper ->
|
||||||
when (wrapper) {
|
when (wrapper) {
|
||||||
is RoomNotification.Removed -> {
|
is RoomNotification.Removed -> {
|
||||||
Timber.d("Removing room messages notification ${wrapper.roomId}")
|
Timber.d("Removing room messages notification ${wrapper.roomId}")
|
||||||
notificationDisplayer.cancelNotificationMessage(wrapper.roomId.value, notificationIdProvider.getRoomMessagesNotificationId(sessionId))
|
notificationDisplayer.cancelNotificationMessage(
|
||||||
|
tag = wrapper.roomId.value,
|
||||||
|
id = notificationIdProvider.getRoomMessagesNotificationId(currentUser.userId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is RoomNotification.Message -> if (useCompleteNotificationFormat) {
|
is RoomNotification.Message -> if (useCompleteNotificationFormat) {
|
||||||
Timber.d("Updating room messages notification ${wrapper.meta.roomId}")
|
Timber.d("Updating room messages notification ${wrapper.meta.roomId}")
|
||||||
notificationDisplayer.showNotificationMessage(
|
notificationDisplayer.showNotificationMessage(
|
||||||
wrapper.meta.roomId.value,
|
tag = wrapper.meta.roomId.value,
|
||||||
notificationIdProvider.getRoomMessagesNotificationId(sessionId),
|
id = notificationIdProvider.getRoomMessagesNotificationId(currentUser.userId),
|
||||||
wrapper.notification
|
notification = wrapper.notification
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -80,14 +82,17 @@ class NotificationRenderer @Inject constructor(
|
||||||
when (wrapper) {
|
when (wrapper) {
|
||||||
is OneShotNotification.Removed -> {
|
is OneShotNotification.Removed -> {
|
||||||
Timber.d("Removing invitation notification ${wrapper.key}")
|
Timber.d("Removing invitation notification ${wrapper.key}")
|
||||||
notificationDisplayer.cancelNotificationMessage(wrapper.key, notificationIdProvider.getRoomInvitationNotificationId(sessionId))
|
notificationDisplayer.cancelNotificationMessage(
|
||||||
|
tag = wrapper.key,
|
||||||
|
id = notificationIdProvider.getRoomInvitationNotificationId(currentUser.userId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is OneShotNotification.Append -> if (useCompleteNotificationFormat) {
|
is OneShotNotification.Append -> if (useCompleteNotificationFormat) {
|
||||||
Timber.d("Updating invitation notification ${wrapper.meta.key}")
|
Timber.d("Updating invitation notification ${wrapper.meta.key}")
|
||||||
notificationDisplayer.showNotificationMessage(
|
notificationDisplayer.showNotificationMessage(
|
||||||
wrapper.meta.key,
|
tag = wrapper.meta.key,
|
||||||
notificationIdProvider.getRoomInvitationNotificationId(sessionId),
|
id = notificationIdProvider.getRoomInvitationNotificationId(currentUser.userId),
|
||||||
wrapper.notification
|
notification = wrapper.notification
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,14 +102,17 @@ class NotificationRenderer @Inject constructor(
|
||||||
when (wrapper) {
|
when (wrapper) {
|
||||||
is OneShotNotification.Removed -> {
|
is OneShotNotification.Removed -> {
|
||||||
Timber.d("Removing simple notification ${wrapper.key}")
|
Timber.d("Removing simple notification ${wrapper.key}")
|
||||||
notificationDisplayer.cancelNotificationMessage(wrapper.key, notificationIdProvider.getRoomEventNotificationId(sessionId))
|
notificationDisplayer.cancelNotificationMessage(
|
||||||
|
tag = wrapper.key,
|
||||||
|
id = notificationIdProvider.getRoomEventNotificationId(currentUser.userId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is OneShotNotification.Append -> if (useCompleteNotificationFormat) {
|
is OneShotNotification.Append -> if (useCompleteNotificationFormat) {
|
||||||
Timber.d("Updating simple notification ${wrapper.meta.key}")
|
Timber.d("Updating simple notification ${wrapper.meta.key}")
|
||||||
notificationDisplayer.showNotificationMessage(
|
notificationDisplayer.showNotificationMessage(
|
||||||
wrapper.meta.key,
|
tag = wrapper.meta.key,
|
||||||
notificationIdProvider.getRoomEventNotificationId(sessionId),
|
id = notificationIdProvider.getRoomEventNotificationId(currentUser.userId),
|
||||||
wrapper.notification
|
notification = wrapper.notification
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -114,9 +122,9 @@ class NotificationRenderer @Inject constructor(
|
||||||
if (summaryNotification is SummaryNotification.Update) {
|
if (summaryNotification is SummaryNotification.Update) {
|
||||||
Timber.d("Updating summary notification")
|
Timber.d("Updating summary notification")
|
||||||
notificationDisplayer.showNotificationMessage(
|
notificationDisplayer.showNotificationMessage(
|
||||||
null,
|
tag = null,
|
||||||
notificationIdProvider.getSummaryNotificationId(sessionId),
|
id = notificationIdProvider.getSummaryNotificationId(currentUser.userId),
|
||||||
summaryNotification.notification
|
notification = summaryNotification.notification
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ import android.graphics.Bitmap
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.Person
|
import androidx.core.app.Person
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.push.impl.R
|
import io.element.android.libraries.push.impl.R
|
||||||
|
import io.element.android.libraries.push.impl.notifications.debug.annotateForDebug
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory
|
import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
|
@ -36,24 +37,22 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
private val notificationFactory: NotificationFactory
|
private val notificationFactory: NotificationFactory
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun createRoomMessage(
|
suspend fun createRoomMessage(
|
||||||
sessionId: SessionId,
|
currentUser: MatrixUser,
|
||||||
events: List<NotifiableMessageEvent>,
|
events: List<NotifiableMessageEvent>,
|
||||||
roomId: RoomId,
|
roomId: RoomId,
|
||||||
userDisplayName: String,
|
|
||||||
userAvatarUrl: String?
|
|
||||||
): RoomNotification.Message {
|
): RoomNotification.Message {
|
||||||
val lastKnownRoomEvent = events.last()
|
val lastKnownRoomEvent = events.last()
|
||||||
val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderName ?: "Room name (${roomId.value.take(8)}…)"
|
val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderName ?: "Room name (${roomId.value.take(8)}…)"
|
||||||
val roomIsGroup = !lastKnownRoomEvent.roomIsDirect
|
val roomIsGroup = !lastKnownRoomEvent.roomIsDirect
|
||||||
val style = NotificationCompat.MessagingStyle(
|
val style = NotificationCompat.MessagingStyle(
|
||||||
Person.Builder()
|
Person.Builder()
|
||||||
.setName(userDisplayName)
|
.setName(currentUser.displayName?.annotateForDebug(50))
|
||||||
.setIcon(bitmapLoader.getUserIcon(userAvatarUrl))
|
.setIcon(bitmapLoader.getUserIcon(currentUser.avatarUrl))
|
||||||
.setKey(lastKnownRoomEvent.sessionId.value)
|
.setKey(lastKnownRoomEvent.sessionId.value)
|
||||||
.build()
|
.build()
|
||||||
).also {
|
).also {
|
||||||
it.conversationTitle = roomName.takeIf { roomIsGroup }
|
it.conversationTitle = roomName.takeIf { roomIsGroup }?.annotateForDebug(51)
|
||||||
it.isGroupConversation = roomIsGroup
|
it.isGroupConversation = roomIsGroup
|
||||||
it.addMessagesFromEvents(events)
|
it.addMessagesFromEvents(events)
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +79,7 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
notificationFactory.createMessagesListNotification(
|
notificationFactory.createMessagesListNotification(
|
||||||
style,
|
style,
|
||||||
RoomEventGroupInfo(
|
RoomEventGroupInfo(
|
||||||
sessionId = sessionId,
|
sessionId = currentUser.userId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
roomDisplayName = roomName,
|
roomDisplayName = roomName,
|
||||||
isDirect = !roomIsGroup,
|
isDirect = !roomIsGroup,
|
||||||
|
|
@ -99,13 +98,13 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotificationCompat.MessagingStyle.addMessagesFromEvents(events: List<NotifiableMessageEvent>) {
|
private suspend fun NotificationCompat.MessagingStyle.addMessagesFromEvents(events: List<NotifiableMessageEvent>) {
|
||||||
events.forEach { event ->
|
events.forEach { event ->
|
||||||
val senderPerson = if (event.outGoingMessage) {
|
val senderPerson = if (event.outGoingMessage) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
Person.Builder()
|
Person.Builder()
|
||||||
.setName(event.senderName)
|
.setName(event.senderName?.annotateForDebug(70))
|
||||||
.setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath))
|
.setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath))
|
||||||
.setKey(event.senderId)
|
.setKey(event.senderId)
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -117,7 +116,11 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
senderPerson
|
senderPerson
|
||||||
)
|
)
|
||||||
else -> {
|
else -> {
|
||||||
val message = NotificationCompat.MessagingStyle.Message(event.body, event.timestamp, senderPerson).also { message ->
|
val message = NotificationCompat.MessagingStyle.Message(
|
||||||
|
event.body?.annotateForDebug(71),
|
||||||
|
event.timestamp,
|
||||||
|
senderPerson
|
||||||
|
).also { message ->
|
||||||
event.imageUri?.let {
|
event.imageUri?.let {
|
||||||
message.setData("image/", it)
|
message.setData("image/", it)
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +171,7 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRoomBitmap(events: List<NotifiableMessageEvent>): Bitmap? {
|
private suspend fun getRoomBitmap(events: List<NotifiableMessageEvent>): Bitmap? {
|
||||||
// Use the last event (most recent?)
|
// Use the last event (most recent?)
|
||||||
return events.lastOrNull()
|
return events.lastOrNull()
|
||||||
?.roomAvatarPath
|
?.roomAvatarPath
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.push.impl.R
|
import io.element.android.libraries.push.impl.R
|
||||||
|
import io.element.android.libraries.push.impl.notifications.debug.annotateForDebug
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory
|
import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory
|
||||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -40,20 +41,20 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class SummaryGroupMessageCreator @Inject constructor(
|
class SummaryGroupMessageCreator @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val notificationFactory: NotificationFactory
|
private val notificationFactory: NotificationFactory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun createSummaryNotification(
|
fun createSummaryNotification(
|
||||||
sessionId: SessionId,
|
currentUser: MatrixUser,
|
||||||
roomNotifications: List<RoomNotification.Message.Meta>,
|
roomNotifications: List<RoomNotification.Message.Meta>,
|
||||||
invitationNotifications: List<OneShotNotification.Append.Meta>,
|
invitationNotifications: List<OneShotNotification.Append.Meta>,
|
||||||
simpleNotifications: List<OneShotNotification.Append.Meta>,
|
simpleNotifications: List<OneShotNotification.Append.Meta>,
|
||||||
useCompleteNotificationFormat: Boolean
|
useCompleteNotificationFormat: Boolean
|
||||||
): Notification {
|
): Notification {
|
||||||
val summaryInboxStyle = NotificationCompat.InboxStyle().also { style ->
|
val summaryInboxStyle = NotificationCompat.InboxStyle().also { style ->
|
||||||
roomNotifications.forEach { style.addLine(it.summaryLine) }
|
roomNotifications.forEach { style.addLine(it.summaryLine.annotateForDebug(40)) }
|
||||||
invitationNotifications.forEach { style.addLine(it.summaryLine) }
|
invitationNotifications.forEach { style.addLine(it.summaryLine.annotateForDebug(41)) }
|
||||||
simpleNotifications.forEach { style.addLine(it.summaryLine) }
|
simpleNotifications.forEach { style.addLine(it.summaryLine.annotateForDebug(42)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val summaryIsNoisy = roomNotifications.any { it.shouldBing } ||
|
val summaryIsNoisy = roomNotifications.any { it.shouldBing } ||
|
||||||
|
|
@ -69,12 +70,13 @@ class SummaryGroupMessageCreator @Inject constructor(
|
||||||
// FIXME roomIdToEventMap.size is not correct, this is the number of rooms
|
// FIXME roomIdToEventMap.size is not correct, this is the number of rooms
|
||||||
val nbEvents = roomNotifications.size + simpleNotifications.size
|
val nbEvents = roomNotifications.size + simpleNotifications.size
|
||||||
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
|
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
|
||||||
summaryInboxStyle.setBigContentTitle(sumTitle)
|
summaryInboxStyle.setBigContentTitle(sumTitle.annotateForDebug(43))
|
||||||
// TODO get latest event?
|
//.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents).annotateForDebug(44))
|
||||||
.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents))
|
// Use account name now, for multi-session
|
||||||
|
.setSummaryText(currentUser.userId.value.annotateForDebug(44))
|
||||||
return if (useCompleteNotificationFormat) {
|
return if (useCompleteNotificationFormat) {
|
||||||
notificationFactory.createSummaryListNotification(
|
notificationFactory.createSummaryListNotification(
|
||||||
sessionId,
|
currentUser,
|
||||||
summaryInboxStyle,
|
summaryInboxStyle,
|
||||||
sumTitle,
|
sumTitle,
|
||||||
noisy = summaryIsNoisy,
|
noisy = summaryIsNoisy,
|
||||||
|
|
@ -82,7 +84,7 @@ class SummaryGroupMessageCreator @Inject constructor(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
processSimpleGroupSummary(
|
processSimpleGroupSummary(
|
||||||
sessionId,
|
currentUser,
|
||||||
summaryIsNoisy,
|
summaryIsNoisy,
|
||||||
messageCount,
|
messageCount,
|
||||||
simpleNotifications.size,
|
simpleNotifications.size,
|
||||||
|
|
@ -94,7 +96,7 @@ class SummaryGroupMessageCreator @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processSimpleGroupSummary(
|
private fun processSimpleGroupSummary(
|
||||||
sessionId: SessionId,
|
currentUser: MatrixUser,
|
||||||
summaryIsNoisy: Boolean,
|
summaryIsNoisy: Boolean,
|
||||||
messageEventsCount: Int,
|
messageEventsCount: Int,
|
||||||
simpleEventsCount: Int,
|
simpleEventsCount: Int,
|
||||||
|
|
@ -167,7 +169,7 @@ class SummaryGroupMessageCreator @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return notificationFactory.createSummaryListNotification(
|
return notificationFactory.createSummaryListNotification(
|
||||||
sessionId = sessionId,
|
currentUser = currentUser,
|
||||||
style = null,
|
style = null,
|
||||||
compatSummary = privacyTitle,
|
compatSummary = privacyTitle,
|
||||||
noisy = summaryIsNoisy,
|
noisy = summaryIsNoisy,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.notifications.debug
|
||||||
|
|
||||||
|
fun CharSequence.annotateForDebug(@Suppress("UNUSED_PARAMETER") prefix: Int): CharSequence {
|
||||||
|
return this // "$prefix-$this"
|
||||||
|
}
|
||||||
|
|
@ -26,11 +26,12 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
|
||||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.push.impl.R
|
import io.element.android.libraries.push.impl.R
|
||||||
import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo
|
import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo
|
||||||
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
|
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
|
||||||
|
import io.element.android.libraries.push.impl.notifications.debug.annotateForDebug
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.action.AcceptInvitationActionFactory
|
import io.element.android.libraries.push.impl.notifications.factories.action.AcceptInvitationActionFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory
|
import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.action.QuickReplyActionFactory
|
import io.element.android.libraries.push.impl.notifications.factories.action.QuickReplyActionFactory
|
||||||
|
|
@ -84,16 +85,16 @@ class NotificationFactory @Inject constructor(
|
||||||
// ID of the corresponding shortcut, for conversation features under API 30+
|
// ID of the corresponding shortcut, for conversation features under API 30+
|
||||||
.setShortcutId(roomInfo.roomId.value)
|
.setShortcutId(roomInfo.roomId.value)
|
||||||
// Title for API < 16 devices.
|
// Title for API < 16 devices.
|
||||||
.setContentTitle(roomInfo.roomDisplayName)
|
.setContentTitle(roomInfo.roomDisplayName.annotateForDebug(1))
|
||||||
// Content for API < 16 devices.
|
// Content for API < 16 devices.
|
||||||
.setContentText(stringProvider.getString(R.string.notification_new_messages))
|
.setContentText(stringProvider.getString(R.string.notification_new_messages).annotateForDebug(2))
|
||||||
// Number of new notifications for API <24 (M and below) devices.
|
// Number of new notifications for API <24 (M and below) devices.
|
||||||
.setSubText(
|
.setSubText(
|
||||||
stringProvider.getQuantityString(
|
stringProvider.getQuantityString(
|
||||||
R.plurals.notification_new_messages_for_room,
|
R.plurals.notification_new_messages_for_room,
|
||||||
messageStyle.messages.size,
|
messageStyle.messages.size,
|
||||||
messageStyle.messages.size
|
messageStyle.messages.size
|
||||||
)
|
).annotateForDebug(3)
|
||||||
)
|
)
|
||||||
// Auto-bundling is enabled for 4 or more notifications on API 24+ (N+)
|
// Auto-bundling is enabled for 4 or more notifications on API 24+ (N+)
|
||||||
// devices and all Wear devices. But we want a custom grouping, so we specify the groupID
|
// devices and all Wear devices. But we want a custom grouping, so we specify the groupID
|
||||||
|
|
@ -135,7 +136,7 @@ class NotificationFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId))
|
setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId))
|
||||||
}
|
}
|
||||||
.setTicker(tickerText)
|
.setTicker(tickerText.annotateForDebug(4))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,8 +148,8 @@ class NotificationFactory @Inject constructor(
|
||||||
val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy)
|
val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy)
|
||||||
return NotificationCompat.Builder(context, channelId)
|
return NotificationCompat.Builder(context, channelId)
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setContentTitle(inviteNotifiableEvent.roomName ?: buildMeta.applicationName)
|
.setContentTitle((inviteNotifiableEvent.roomName ?: buildMeta.applicationName).annotateForDebug(5))
|
||||||
.setContentText(inviteNotifiableEvent.description)
|
.setContentText(inviteNotifiableEvent.description.annotateForDebug(6))
|
||||||
.setGroup(inviteNotifiableEvent.sessionId.value)
|
.setGroup(inviteNotifiableEvent.sessionId.value)
|
||||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
||||||
.setSmallIcon(smallIcon)
|
.setSmallIcon(smallIcon)
|
||||||
|
|
@ -196,8 +197,8 @@ class NotificationFactory @Inject constructor(
|
||||||
val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy)
|
val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy)
|
||||||
return NotificationCompat.Builder(context, channelId)
|
return NotificationCompat.Builder(context, channelId)
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setContentTitle(buildMeta.applicationName)
|
.setContentTitle(buildMeta.applicationName.annotateForDebug(7))
|
||||||
.setContentText(simpleNotifiableEvent.description)
|
.setContentText(simpleNotifiableEvent.description.annotateForDebug(8))
|
||||||
.setGroup(simpleNotifiableEvent.sessionId.value)
|
.setGroup(simpleNotifiableEvent.sessionId.value)
|
||||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
||||||
.setSmallIcon(smallIcon)
|
.setSmallIcon(smallIcon)
|
||||||
|
|
@ -226,7 +227,7 @@ class NotificationFactory @Inject constructor(
|
||||||
* Create the summary notification.
|
* Create the summary notification.
|
||||||
*/
|
*/
|
||||||
fun createSummaryListNotification(
|
fun createSummaryListNotification(
|
||||||
sessionId: SessionId,
|
currentUser: MatrixUser,
|
||||||
style: NotificationCompat.InboxStyle?,
|
style: NotificationCompat.InboxStyle?,
|
||||||
compatSummary: String,
|
compatSummary: String,
|
||||||
noisy: Boolean,
|
noisy: Boolean,
|
||||||
|
|
@ -240,12 +241,12 @@ class NotificationFactory @Inject constructor(
|
||||||
// used in compat < N, after summary is built based on child notifications
|
// used in compat < N, after summary is built based on child notifications
|
||||||
.setWhen(lastMessageTimestamp)
|
.setWhen(lastMessageTimestamp)
|
||||||
.setStyle(style)
|
.setStyle(style)
|
||||||
.setContentTitle(sessionId.value)
|
.setContentTitle(currentUser.userId.value.annotateForDebug(9))
|
||||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||||
.setSmallIcon(smallIcon)
|
.setSmallIcon(smallIcon)
|
||||||
// set content text to support devices running API level < 24
|
// set content text to support devices running API level < 24
|
||||||
.setContentText(compatSummary)
|
.setContentText(compatSummary.annotateForDebug(10))
|
||||||
.setGroup(sessionId.value)
|
.setGroup(currentUser.userId.value)
|
||||||
// set this notification as the summary for the group
|
// set this notification as the summary for the group
|
||||||
.setGroupSummary(true)
|
.setGroupSummary(true)
|
||||||
.setColor(accentColor)
|
.setColor(accentColor)
|
||||||
|
|
@ -264,8 +265,8 @@ class NotificationFactory @Inject constructor(
|
||||||
priority = NotificationCompat.PRIORITY_LOW
|
priority = NotificationCompat.PRIORITY_LOW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(sessionId))
|
.setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(currentUser.userId))
|
||||||
.setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(sessionId))
|
.setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(currentUser.userId))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,9 @@ data class NotifiableMessageEvent(
|
||||||
val description: String = body ?: ""
|
val description: String = body ?: ""
|
||||||
val title: String = senderName ?: ""
|
val title: String = senderName ?: ""
|
||||||
|
|
||||||
|
// TODO EAx The image has to be downloaded and expose using the file provider.
|
||||||
|
// Example of value from Element Android:
|
||||||
|
// content://im.vector.app.debug.mx-sdk.fileprovider/downloads/downloads/816abf76d806c768760568952b1862c8/F/72c33edd23dee3b95f4d5a18aa25fa54/image.png
|
||||||
val imageUri: Uri?
|
val imageUri: Uri?
|
||||||
get() = imageUriString?.let { Uri.parse(it) }
|
get() = imageUriString?.let { Uri.parse(it) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
|
@ -27,6 +28,7 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGrou
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
private val MY_AVATAR_URL: String? = null
|
private val MY_AVATAR_URL: String? = null
|
||||||
|
|
@ -124,11 +126,13 @@ class NotificationFactoryTest {
|
||||||
fun `given room with message when mapping to notification then delegates to room group message creator`() = testWith(notificationFactory) {
|
fun `given room with message when mapping to notification then delegates to room group message creator`() = testWith(notificationFactory) {
|
||||||
val events = listOf(A_MESSAGE_EVENT)
|
val events = listOf(A_MESSAGE_EVENT)
|
||||||
val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor(
|
val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor(
|
||||||
A_SESSION_ID, events, A_ROOM_ID, A_SESSION_ID.value, MY_AVATAR_URL
|
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), events, A_ROOM_ID
|
||||||
)
|
)
|
||||||
val roomWithMessage = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT)))
|
val roomWithMessage = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT)))
|
||||||
|
|
||||||
val result = roomWithMessage.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
val result = roomWithMessage.toNotifications(
|
||||||
|
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
||||||
|
)
|
||||||
|
|
||||||
assertThat(result).isEqualTo(listOf(expectedNotification))
|
assertThat(result).isEqualTo(listOf(expectedNotification))
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +142,9 @@ class NotificationFactoryTest {
|
||||||
val events = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, A_MESSAGE_EVENT))
|
val events = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, A_MESSAGE_EVENT))
|
||||||
val emptyRoom = mapOf(A_ROOM_ID to events)
|
val emptyRoom = mapOf(A_ROOM_ID to events)
|
||||||
|
|
||||||
val result = emptyRoom.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
val result = emptyRoom.toNotifications(
|
||||||
|
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
||||||
|
)
|
||||||
|
|
||||||
assertThat(result).isEqualTo(
|
assertThat(result).isEqualTo(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -153,7 +159,9 @@ class NotificationFactoryTest {
|
||||||
fun `given a room with only redacted events when mapping to notification then is Empty`() = testWith(notificationFactory) {
|
fun `given a room with only redacted events when mapping to notification then is Empty`() = testWith(notificationFactory) {
|
||||||
val redactedRoom = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(isRedacted = true))))
|
val redactedRoom = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(isRedacted = true))))
|
||||||
|
|
||||||
val result = redactedRoom.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
val result = redactedRoom.toNotifications(
|
||||||
|
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
||||||
|
)
|
||||||
|
|
||||||
assertThat(result).isEqualTo(
|
assertThat(result).isEqualTo(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -176,19 +184,21 @@ class NotificationFactoryTest {
|
||||||
)
|
)
|
||||||
val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("\$not-redacted")))
|
val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("\$not-redacted")))
|
||||||
val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor(
|
val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor(
|
||||||
A_SESSION_ID,
|
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL),
|
||||||
withRedactedRemoved,
|
withRedactedRemoved,
|
||||||
A_ROOM_ID,
|
A_ROOM_ID,
|
||||||
A_SESSION_ID.value,
|
|
||||||
MY_AVATAR_URL
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = roomWithRedactedMessage.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
val result = roomWithRedactedMessage.toNotifications(
|
||||||
|
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
||||||
|
)
|
||||||
|
|
||||||
assertThat(result).isEqualTo(listOf(expectedNotification))
|
assertThat(result).isEqualTo(listOf(expectedNotification))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> testWith(receiver: T, block: T.() -> Unit) {
|
fun <T> testWith(receiver: T, block: suspend T.() -> Unit) {
|
||||||
receiver.block()
|
runTest {
|
||||||
|
receiver.block()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.libraries.push.impl.notifications
|
package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
|
@ -24,6 +25,7 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeNotificatio
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationFactory
|
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
private const val MY_USER_DISPLAY_NAME = "display-name"
|
private const val MY_USER_DISPLAY_NAME = "display-name"
|
||||||
|
|
@ -53,7 +55,7 @@ class NotificationRendererTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given no notifications when rendering then cancels summary notification`() {
|
fun `given no notifications when rendering then cancels summary notification`() = runTest {
|
||||||
givenNoNotifications()
|
givenNoNotifications()
|
||||||
|
|
||||||
renderEventsAsNotifications()
|
renderEventsAsNotifications()
|
||||||
|
|
@ -63,7 +65,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given last room message group notification is removed when rendering then remove the summary and then remove message notification`() {
|
fun `given last room message group notification is removed when rendering then remove the summary and then remove message notification`() = runTest {
|
||||||
givenNotifications(roomNotifications = listOf(RoomNotification.Removed(A_ROOM_ID)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
givenNotifications(roomNotifications = listOf(RoomNotification.Removed(A_ROOM_ID)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
||||||
|
|
||||||
renderEventsAsNotifications()
|
renderEventsAsNotifications()
|
||||||
|
|
@ -75,7 +77,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a room message group notification is removed when rendering then remove the message notification and update summary`() {
|
fun `given a room message group notification is removed when rendering then remove the message notification and update summary`() = runTest {
|
||||||
givenNotifications(roomNotifications = listOf(RoomNotification.Removed(A_ROOM_ID)))
|
givenNotifications(roomNotifications = listOf(RoomNotification.Removed(A_ROOM_ID)))
|
||||||
|
|
||||||
renderEventsAsNotifications()
|
renderEventsAsNotifications()
|
||||||
|
|
@ -87,7 +89,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a room message group notification is added when rendering then show the message notification and update summary`() {
|
fun `given a room message group notification is added when rendering then show the message notification and update summary`() = runTest {
|
||||||
givenNotifications(
|
givenNotifications(
|
||||||
roomNotifications = listOf(
|
roomNotifications = listOf(
|
||||||
RoomNotification.Message(
|
RoomNotification.Message(
|
||||||
|
|
@ -106,7 +108,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given last simple notification is removed when rendering then remove the summary and then remove simple notification`() {
|
fun `given last simple notification is removed when rendering then remove the summary and then remove simple notification`() = runTest {
|
||||||
givenNotifications(simpleNotifications = listOf(OneShotNotification.Removed(AN_EVENT_ID.value)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
givenNotifications(simpleNotifications = listOf(OneShotNotification.Removed(AN_EVENT_ID.value)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
||||||
|
|
||||||
renderEventsAsNotifications()
|
renderEventsAsNotifications()
|
||||||
|
|
@ -118,7 +120,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a simple notification is removed when rendering then remove the simple notification and update summary`() {
|
fun `given a simple notification is removed when rendering then remove the simple notification and update summary`() = runTest {
|
||||||
givenNotifications(simpleNotifications = listOf(OneShotNotification.Removed(AN_EVENT_ID.value)))
|
givenNotifications(simpleNotifications = listOf(OneShotNotification.Removed(AN_EVENT_ID.value)))
|
||||||
|
|
||||||
renderEventsAsNotifications()
|
renderEventsAsNotifications()
|
||||||
|
|
@ -130,7 +132,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a simple notification is added when rendering then show the simple notification and update summary`() {
|
fun `given a simple notification is added when rendering then show the simple notification and update summary`() = runTest {
|
||||||
givenNotifications(
|
givenNotifications(
|
||||||
simpleNotifications = listOf(
|
simpleNotifications = listOf(
|
||||||
OneShotNotification.Append(
|
OneShotNotification.Append(
|
||||||
|
|
@ -149,7 +151,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given last invitation notification is removed when rendering then remove the summary and then remove invitation notification`() {
|
fun `given last invitation notification is removed when rendering then remove the summary and then remove invitation notification`() = runTest {
|
||||||
givenNotifications(invitationNotifications = listOf(OneShotNotification.Removed(A_ROOM_ID.value)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
givenNotifications(invitationNotifications = listOf(OneShotNotification.Removed(A_ROOM_ID.value)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
||||||
|
|
||||||
renderEventsAsNotifications()
|
renderEventsAsNotifications()
|
||||||
|
|
@ -161,7 +163,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given an invitation notification is removed when rendering then remove the invitation notification and update summary`() {
|
fun `given an invitation notification is removed when rendering then remove the invitation notification and update summary`() = runTest {
|
||||||
givenNotifications(invitationNotifications = listOf(OneShotNotification.Removed(A_ROOM_ID.value)))
|
givenNotifications(invitationNotifications = listOf(OneShotNotification.Removed(A_ROOM_ID.value)))
|
||||||
|
|
||||||
renderEventsAsNotifications()
|
renderEventsAsNotifications()
|
||||||
|
|
@ -173,7 +175,7 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given an invitation notification is added when rendering then show the invitation notification and update summary`() {
|
fun `given an invitation notification is added when rendering then show the invitation notification and update summary`() = runTest {
|
||||||
givenNotifications(
|
givenNotifications(
|
||||||
simpleNotifications = listOf(
|
simpleNotifications = listOf(
|
||||||
OneShotNotification.Append(
|
OneShotNotification.Append(
|
||||||
|
|
@ -191,11 +193,9 @@ class NotificationRendererTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderEventsAsNotifications() {
|
private suspend fun renderEventsAsNotifications() {
|
||||||
notificationRenderer.render(
|
notificationRenderer.render(
|
||||||
sessionId = A_SESSION_ID,
|
MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL),
|
||||||
myUserDisplayName = MY_USER_DISPLAY_NAME,
|
|
||||||
myUserAvatarUrl = MY_USER_AVATAR_URL,
|
|
||||||
useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT,
|
useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT,
|
||||||
eventsToProcess = AN_EVENT_LIST
|
eventsToProcess = AN_EVENT_LIST
|
||||||
)
|
)
|
||||||
|
|
@ -214,9 +214,7 @@ class NotificationRendererTest {
|
||||||
) {
|
) {
|
||||||
notificationFactory.givenNotificationsFor(
|
notificationFactory.givenNotificationsFor(
|
||||||
groupedEvents = A_PROCESSED_EVENTS,
|
groupedEvents = A_PROCESSED_EVENTS,
|
||||||
sessionId = A_SESSION_ID,
|
matrixUser = MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL),
|
||||||
myUserDisplayName = MY_USER_DISPLAY_NAME,
|
|
||||||
myUserAvatarUrl = MY_USER_AVATAR_URL,
|
|
||||||
useCompleteNotificationFormat = useCompleteNotificationFormat,
|
useCompleteNotificationFormat = useCompleteNotificationFormat,
|
||||||
roomNotifications = roomNotifications,
|
roomNotifications = roomNotifications,
|
||||||
invitationNotifications = invitationNotifications,
|
invitationNotifications = invitationNotifications,
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@
|
||||||
|
|
||||||
package io.element.android.libraries.push.impl.notifications.fake
|
package io.element.android.libraries.push.impl.notifications.fake
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.push.impl.notifications.GroupedNotificationEvents
|
import io.element.android.libraries.push.impl.notifications.GroupedNotificationEvents
|
||||||
import io.element.android.libraries.push.impl.notifications.NotificationFactory
|
import io.element.android.libraries.push.impl.notifications.NotificationFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.OneShotNotification
|
import io.element.android.libraries.push.impl.notifications.OneShotNotification
|
||||||
import io.element.android.libraries.push.impl.notifications.RoomNotification
|
import io.element.android.libraries.push.impl.notifications.RoomNotification
|
||||||
import io.element.android.libraries.push.impl.notifications.SummaryNotification
|
import io.element.android.libraries.push.impl.notifications.SummaryNotification
|
||||||
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
|
||||||
|
|
@ -30,9 +31,7 @@ class FakeNotificationFactory {
|
||||||
|
|
||||||
fun givenNotificationsFor(
|
fun givenNotificationsFor(
|
||||||
groupedEvents: GroupedNotificationEvents,
|
groupedEvents: GroupedNotificationEvents,
|
||||||
sessionId: SessionId,
|
matrixUser: MatrixUser,
|
||||||
myUserDisplayName: String,
|
|
||||||
myUserAvatarUrl: String?,
|
|
||||||
useCompleteNotificationFormat: Boolean,
|
useCompleteNotificationFormat: Boolean,
|
||||||
roomNotifications: List<RoomNotification>,
|
roomNotifications: List<RoomNotification>,
|
||||||
invitationNotifications: List<OneShotNotification>,
|
invitationNotifications: List<OneShotNotification>,
|
||||||
|
|
@ -40,13 +39,13 @@ class FakeNotificationFactory {
|
||||||
summaryNotification: SummaryNotification
|
summaryNotification: SummaryNotification
|
||||||
) {
|
) {
|
||||||
with(instance) {
|
with(instance) {
|
||||||
every { groupedEvents.roomEvents.toNotifications(sessionId, myUserDisplayName, myUserAvatarUrl) } returns roomNotifications
|
coEvery { groupedEvents.roomEvents.toNotifications(matrixUser) } returns roomNotifications
|
||||||
every { groupedEvents.invitationEvents.toNotifications() } returns invitationNotifications
|
every { groupedEvents.invitationEvents.toNotifications() } returns invitationNotifications
|
||||||
every { groupedEvents.simpleEvents.toNotifications() } returns simpleNotifications
|
every { groupedEvents.simpleEvents.toNotifications() } returns simpleNotifications
|
||||||
|
|
||||||
every {
|
every {
|
||||||
createSummaryNotification(
|
createSummaryNotification(
|
||||||
sessionId,
|
matrixUser,
|
||||||
roomNotifications,
|
roomNotifications,
|
||||||
invitationNotifications,
|
invitationNotifications,
|
||||||
simpleNotifications,
|
simpleNotifications,
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@
|
||||||
package io.element.android.libraries.push.impl.notifications.fake
|
package io.element.android.libraries.push.impl.notifications.fake
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.push.impl.notifications.RoomGroupMessageCreator
|
import io.element.android.libraries.push.impl.notifications.RoomGroupMessageCreator
|
||||||
import io.element.android.libraries.push.impl.notifications.RoomNotification
|
import io.element.android.libraries.push.impl.notifications.RoomNotification
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
import io.mockk.every
|
import io.mockk.coEvery
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
|
||||||
class FakeRoomGroupMessageCreator {
|
class FakeRoomGroupMessageCreator {
|
||||||
|
|
@ -29,14 +29,18 @@ class FakeRoomGroupMessageCreator {
|
||||||
val instance = mockk<RoomGroupMessageCreator>()
|
val instance = mockk<RoomGroupMessageCreator>()
|
||||||
|
|
||||||
fun givenCreatesRoomMessageFor(
|
fun givenCreatesRoomMessageFor(
|
||||||
sessionId: SessionId,
|
matrixUser: MatrixUser,
|
||||||
events: List<NotifiableMessageEvent>,
|
events: List<NotifiableMessageEvent>,
|
||||||
roomId: RoomId,
|
roomId: RoomId,
|
||||||
userDisplayName: String,
|
|
||||||
userAvatarUrl: String?
|
|
||||||
): RoomNotification.Message {
|
): RoomNotification.Message {
|
||||||
val mockMessage = mockk<RoomNotification.Message>()
|
val mockMessage = mockk<RoomNotification.Message>()
|
||||||
every { instance.createRoomMessage(sessionId, events, roomId, userDisplayName, userAvatarUrl) } returns mockMessage
|
coEvery {
|
||||||
|
instance.createRoomMessage(
|
||||||
|
currentUser = matrixUser,
|
||||||
|
events = events,
|
||||||
|
roomId = roomId,
|
||||||
|
)
|
||||||
|
} returns mockMessage
|
||||||
return mockMessage
|
return mockMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue