creating the notifications separate to where they're displayed
- also handles when the event diff means the notifications should be removed
This commit is contained in:
parent
7b0c483134
commit
0f4ec65b7a
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 im.vector.app.features.notifications
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class NotificationFactory @Inject constructor(
|
||||||
|
private val notificationUtils: NotificationUtils,
|
||||||
|
private val roomGroupMessageCreator: RoomGroupMessageCreator,
|
||||||
|
private val summaryGroupMessageCreator: SummaryGroupMessageCreator
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun Map<String, List<NotifiableMessageEvent>>.toNotifications(myUserDisplayName: String, myUserAvatarUrl: String?): List<RoomNotification> {
|
||||||
|
return this.map { (roomId, events) ->
|
||||||
|
when {
|
||||||
|
events.hasNoEventsToDisplay() -> RoomNotification.EmptyRoom(roomId)
|
||||||
|
else -> roomGroupMessageCreator.createRoomMessage(events, roomId, myUserDisplayName, myUserAvatarUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<NotifiableMessageEvent>.hasNoEventsToDisplay() = isEmpty() || all { it.canNotBeDisplayed() }
|
||||||
|
|
||||||
|
private fun NotifiableMessageEvent.canNotBeDisplayed() = hasBeenDisplayed || isRedacted
|
||||||
|
|
||||||
|
fun Map<String, InviteNotifiableEvent?>.toNotifications(myUserId: String): List<OneShotNotification> {
|
||||||
|
return this.map { (roomId, event) ->
|
||||||
|
when (event) {
|
||||||
|
null -> OneShotNotification.Removed(key = roomId)
|
||||||
|
else -> OneShotNotification.Append(
|
||||||
|
notificationUtils.buildRoomInvitationNotification(event, myUserId),
|
||||||
|
OneShotNotification.Append.Meta(key = roomId, summaryLine = event.description, isNoisy = event.noisy)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("toNotificationsSimpleNotifiableEvent")
|
||||||
|
fun Map<String, SimpleNotifiableEvent?>.toNotifications(myUserId: String): List<OneShotNotification> {
|
||||||
|
return this.map { (eventId, event) ->
|
||||||
|
when (event) {
|
||||||
|
null -> OneShotNotification.Removed(key = eventId)
|
||||||
|
else -> OneShotNotification.Append(
|
||||||
|
notificationUtils.buildSimpleEventNotification(event, myUserId),
|
||||||
|
OneShotNotification.Append.Meta(key = eventId, summaryLine = event.description, isNoisy = event.noisy)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createSummaryNotification(roomNotifications: List<RoomNotification>,
|
||||||
|
invitationNotifications: List<OneShotNotification>,
|
||||||
|
simpleNotifications: List<OneShotNotification>,
|
||||||
|
useCompleteNotificationFormat: Boolean): Notification {
|
||||||
|
return summaryGroupMessageCreator.createSummaryNotification(
|
||||||
|
roomNotifications = roomNotifications.mapToMeta(),
|
||||||
|
invitationNotifications = invitationNotifications.mapToMeta(),
|
||||||
|
simpleNotifications = simpleNotifications.mapToMeta(),
|
||||||
|
useCompleteNotificationFormat = useCompleteNotificationFormat
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<RoomNotification>.mapToMeta() = filterIsInstance<RoomNotification.Message>().map { it.meta }
|
||||||
|
|
||||||
|
@JvmName("mapToMetaOneShotNotification")
|
||||||
|
private fun List<OneShotNotification>.mapToMeta() = filterIsInstance<OneShotNotification.Append>().map { it.meta }
|
||||||
|
|
||||||
|
sealed interface RoomNotification {
|
||||||
|
data class EmptyRoom(val roomId: String) : RoomNotification
|
||||||
|
data class Message(val notification: Notification, val meta: Meta) : RoomNotification {
|
||||||
|
data class Meta(
|
||||||
|
val summaryLine: CharSequence,
|
||||||
|
val messageCount: Int,
|
||||||
|
val latestTimestamp: Long,
|
||||||
|
val roomId: String,
|
||||||
|
val shouldBing: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface OneShotNotification {
|
||||||
|
data class Removed(val key: String) : OneShotNotification
|
||||||
|
data class Append(val notification: Notification, val meta: Meta) : OneShotNotification {
|
||||||
|
data class Meta(
|
||||||
|
val key: String,
|
||||||
|
val summaryLine: CharSequence,
|
||||||
|
val isNoisy: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 im.vector.app.features.notifications
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.Person
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import me.gujun.android.span.Span
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomGroupMessageCreator @Inject constructor(
|
||||||
|
private val iconLoader: IconLoader,
|
||||||
|
private val bitmapLoader: BitmapLoader,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val notificationUtils: NotificationUtils
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun createRoomMessage(events: List<NotifiableMessageEvent>, roomId: String, userDisplayName: String, userAvatarUrl: String?): RoomNotification.Message {
|
||||||
|
val firstKnownRoomEvent = events[0]
|
||||||
|
val roomName = firstKnownRoomEvent.roomName ?: firstKnownRoomEvent.senderName ?: ""
|
||||||
|
val roomIsGroup = !firstKnownRoomEvent.roomIsDirect
|
||||||
|
val style = NotificationCompat.MessagingStyle(Person.Builder()
|
||||||
|
.setName(userDisplayName)
|
||||||
|
.setIcon(iconLoader.getUserIcon(userAvatarUrl))
|
||||||
|
.setKey(firstKnownRoomEvent.matrixID)
|
||||||
|
.build()
|
||||||
|
).also {
|
||||||
|
it.conversationTitle = roomName.takeIf { roomIsGroup }
|
||||||
|
it.isGroupConversation = roomIsGroup
|
||||||
|
it.addMessagesFromEvents(events)
|
||||||
|
}
|
||||||
|
|
||||||
|
val tickerText = if (roomIsGroup) {
|
||||||
|
stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description)
|
||||||
|
} else {
|
||||||
|
stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description)
|
||||||
|
}
|
||||||
|
|
||||||
|
val lastMessageTimestamp = events.last().timestamp
|
||||||
|
val smartReplyErrors = events.filter { it.isSmartReplyError() }
|
||||||
|
val messageCount = (events.size - smartReplyErrors.size)
|
||||||
|
val meta = RoomNotification.Message.Meta(
|
||||||
|
summaryLine = createRoomMessagesGroupSummaryLine(events, roomName, roomIsDirect = !roomIsGroup),
|
||||||
|
messageCount = messageCount,
|
||||||
|
latestTimestamp = lastMessageTimestamp,
|
||||||
|
roomId = roomId,
|
||||||
|
shouldBing = events.any { it.noisy }
|
||||||
|
)
|
||||||
|
return RoomNotification.Message(
|
||||||
|
notificationUtils.buildMessagesListNotification(
|
||||||
|
style,
|
||||||
|
RoomEventGroupInfo(roomId, roomName, isDirect = !roomIsGroup).also {
|
||||||
|
it.hasSmartReplyError = smartReplyErrors.isNotEmpty()
|
||||||
|
it.shouldBing = meta.shouldBing
|
||||||
|
it.customSound = events.last().soundName
|
||||||
|
},
|
||||||
|
largeIcon = getRoomBitmap(events),
|
||||||
|
lastMessageTimestamp,
|
||||||
|
userDisplayName,
|
||||||
|
tickerText
|
||||||
|
),
|
||||||
|
meta
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationCompat.MessagingStyle.addMessagesFromEvents(events: List<NotifiableMessageEvent>) {
|
||||||
|
events.forEach { event ->
|
||||||
|
val senderPerson = Person.Builder()
|
||||||
|
.setName(event.senderName)
|
||||||
|
.setIcon(iconLoader.getUserIcon(event.senderAvatarPath))
|
||||||
|
.setKey(event.senderId)
|
||||||
|
.build()
|
||||||
|
when {
|
||||||
|
event.isSmartReplyError() -> addMessage(stringProvider.getString(R.string.notification_inline_reply_failed), event.timestamp, senderPerson)
|
||||||
|
else -> addMessage(event.body, event.timestamp, senderPerson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRoomMessagesGroupSummaryLine(events: List<NotifiableMessageEvent>, roomName: String, roomIsDirect: Boolean): CharSequence {
|
||||||
|
return try {
|
||||||
|
when (events.size) {
|
||||||
|
1 -> createFirstMessageSummaryLine(events.first(), roomName, roomIsDirect)
|
||||||
|
else -> {
|
||||||
|
stringProvider.getQuantityString(
|
||||||
|
R.plurals.notification_compat_summary_line_for_room,
|
||||||
|
events.size,
|
||||||
|
roomName,
|
||||||
|
events.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// String not found or bad format
|
||||||
|
Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER failed to resolve string")
|
||||||
|
roomName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFirstMessageSummaryLine(event: NotifiableMessageEvent, roomName: String, roomIsDirect: Boolean): Span {
|
||||||
|
return if (roomIsDirect) {
|
||||||
|
span {
|
||||||
|
span {
|
||||||
|
textStyle = "bold"
|
||||||
|
+String.format("%s: ", event.senderName)
|
||||||
|
}
|
||||||
|
+(event.description)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
span {
|
||||||
|
span {
|
||||||
|
textStyle = "bold"
|
||||||
|
+String.format("%s: %s ", roomName, event.senderName)
|
||||||
|
}
|
||||||
|
+(event.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRoomBitmap(events: List<NotifiableMessageEvent>): Bitmap? {
|
||||||
|
if (events.isEmpty()) return null
|
||||||
|
|
||||||
|
// Use the last event (most recent?)
|
||||||
|
val roomAvatarPath = events.last().roomAvatarPath ?: events.last().senderAvatarPath
|
||||||
|
|
||||||
|
return bitmapLoader.getRoomBitmap(roomAvatarPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotifiableMessageEvent.isSmartReplyError() = this.outGoingMessage && this.outGoingMessageFailed
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 im.vector.app.features.notifications
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SummaryGroupMessageCreator @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val notificationUtils: NotificationUtils
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ======== Build summary notification =========
|
||||||
|
* On Android 7.0 (API level 24) and higher, the system automatically builds a summary for
|
||||||
|
* your group using snippets of text from each notification. The user can expand this
|
||||||
|
* notification to see each separate notification.
|
||||||
|
* To support older versions, which cannot show a nested group of notifications,
|
||||||
|
* you must create an extra notification that acts as the summary.
|
||||||
|
* This appears as the only notification and the system hides all the others.
|
||||||
|
* So this summary should include a snippet from all the other notifications,
|
||||||
|
* which the user can tap to open your app.
|
||||||
|
* The behavior of the group summary may vary on some device types such as wearables.
|
||||||
|
* To ensure the best experience on all devices and versions, always include a group summary when you create a group
|
||||||
|
* https://developer.android.com/training/notify-user/group
|
||||||
|
*/
|
||||||
|
fun createSummaryNotification(roomNotifications: List<RoomNotification.Message.Meta>,
|
||||||
|
invitationNotifications: List<OneShotNotification.Append.Meta>,
|
||||||
|
simpleNotifications: List<OneShotNotification.Append.Meta>,
|
||||||
|
useCompleteNotificationFormat: Boolean): Notification {
|
||||||
|
val summaryInboxStyle = NotificationCompat.InboxStyle().also { style ->
|
||||||
|
roomNotifications.forEach { style.addLine(it.summaryLine) }
|
||||||
|
invitationNotifications.forEach { style.addLine(it.summaryLine) }
|
||||||
|
simpleNotifications.forEach { style.addLine(it.summaryLine) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val summaryIsNoisy = roomNotifications.any { it.shouldBing }
|
||||||
|
|| invitationNotifications.any { it.isNoisy }
|
||||||
|
|| simpleNotifications.any { it.isNoisy }
|
||||||
|
|
||||||
|
val messageCount = roomNotifications.fold(initial = 0) { acc, current -> acc + current.messageCount }
|
||||||
|
|
||||||
|
val lastMessageTimestamp1 = roomNotifications.last().latestTimestamp
|
||||||
|
|
||||||
|
// FIXME roomIdToEventMap.size is not correct, this is the number of rooms
|
||||||
|
val nbEvents = roomNotifications.size + simpleNotifications.size
|
||||||
|
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
|
||||||
|
summaryInboxStyle.setBigContentTitle(sumTitle)
|
||||||
|
// TODO get latest event?
|
||||||
|
.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents))
|
||||||
|
return if (useCompleteNotificationFormat
|
||||||
|
) {
|
||||||
|
notificationUtils.buildSummaryListNotification(
|
||||||
|
summaryInboxStyle,
|
||||||
|
sumTitle,
|
||||||
|
noisy = summaryIsNoisy,
|
||||||
|
lastMessageTimestamp = lastMessageTimestamp1
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
processSimpleGroupSummary(summaryIsNoisy, messageCount,
|
||||||
|
simpleNotifications.size, invitationNotifications.size,
|
||||||
|
roomNotifications.size, lastMessageTimestamp1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processSimpleGroupSummary(summaryIsNoisy: Boolean,
|
||||||
|
messageEventsCount: Int,
|
||||||
|
simpleEventsCount: Int,
|
||||||
|
invitationEventsCount: Int,
|
||||||
|
roomCount: Int,
|
||||||
|
lastMessageTimestamp: Long): Notification {
|
||||||
|
// Add the simple events as message (?)
|
||||||
|
val messageNotificationCount = messageEventsCount + simpleEventsCount
|
||||||
|
|
||||||
|
val privacyTitle = if (invitationEventsCount > 0) {
|
||||||
|
val invitationsStr = stringProvider.getQuantityString(R.plurals.notification_invitations, invitationEventsCount, invitationEventsCount)
|
||||||
|
if (messageNotificationCount > 0) {
|
||||||
|
// Invitation and message
|
||||||
|
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
|
||||||
|
messageNotificationCount, messageNotificationCount)
|
||||||
|
if (roomCount > 1) {
|
||||||
|
// In several rooms
|
||||||
|
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
|
||||||
|
roomCount, roomCount)
|
||||||
|
stringProvider.getString(
|
||||||
|
R.string.notification_unread_notified_messages_in_room_and_invitation,
|
||||||
|
messageStr,
|
||||||
|
roomStr,
|
||||||
|
invitationsStr
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// In one room
|
||||||
|
stringProvider.getString(
|
||||||
|
R.string.notification_unread_notified_messages_and_invitation,
|
||||||
|
messageStr,
|
||||||
|
invitationsStr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only invitation
|
||||||
|
invitationsStr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No invitation, only messages
|
||||||
|
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
|
||||||
|
messageNotificationCount, messageNotificationCount)
|
||||||
|
if (roomCount > 1) {
|
||||||
|
// In several rooms
|
||||||
|
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms, roomCount, roomCount)
|
||||||
|
stringProvider.getString(R.string.notification_unread_notified_messages_in_room, messageStr, roomStr)
|
||||||
|
} else {
|
||||||
|
// In one room
|
||||||
|
messageStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notificationUtils.buildSummaryListNotification(
|
||||||
|
style = null,
|
||||||
|
compatSummary = privacyTitle,
|
||||||
|
noisy = summaryIsNoisy,
|
||||||
|
lastMessageTimestamp = lastMessageTimestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 im.vector.app.features.notifications
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeNotificationUtils
|
||||||
|
import im.vector.app.test.fakes.FakeRoomGroupMessageCreator
|
||||||
|
import im.vector.app.test.fakes.FakeSummaryGroupMessageCreator
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
private const val MY_USER_ID = "user-id"
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
private const val AN_EVENT_ID = "event-id"
|
||||||
|
|
||||||
|
private val MY_AVATAR_URL: String? = null
|
||||||
|
private val AN_INVITATION_EVENT = anInviteNotifiableEvent(roomId = A_ROOM_ID)
|
||||||
|
private val A_SIMPLE_EVENT = aSimpleNotifiableEvent(eventId = AN_EVENT_ID)
|
||||||
|
private val A_MESSAGE_EVENT = aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)
|
||||||
|
|
||||||
|
class NotificationFactoryTest {
|
||||||
|
|
||||||
|
private val notificationUtils = FakeNotificationUtils()
|
||||||
|
private val roomGroupMessageCreator = FakeRoomGroupMessageCreator()
|
||||||
|
private val summaryGroupMessageCreator = FakeSummaryGroupMessageCreator()
|
||||||
|
|
||||||
|
private val notificationFactory = NotificationFactory(
|
||||||
|
notificationUtils.instance,
|
||||||
|
roomGroupMessageCreator.instance,
|
||||||
|
summaryGroupMessageCreator.instance
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a room invitation when mapping to notification then is Append`() = testWith(notificationFactory) {
|
||||||
|
val expectedNotification = notificationUtils.givenBuildRoomInvitationNotificationFor(AN_INVITATION_EVENT, MY_USER_ID)
|
||||||
|
val roomInvitation = mapOf(A_ROOM_ID to AN_INVITATION_EVENT)
|
||||||
|
|
||||||
|
val result = roomInvitation.toNotifications(MY_USER_ID)
|
||||||
|
|
||||||
|
result shouldBeEqualTo listOf(OneShotNotification.Append(
|
||||||
|
notification = expectedNotification,
|
||||||
|
meta = OneShotNotification.Append.Meta(
|
||||||
|
key = A_ROOM_ID,
|
||||||
|
summaryLine = AN_INVITATION_EVENT.description,
|
||||||
|
isNoisy = AN_INVITATION_EVENT.noisy
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a missing event in room invitation when mapping to notification then is Removed`() = testWith(notificationFactory) {
|
||||||
|
val missingEventRoomInvitation: Map<String, InviteNotifiableEvent?> = mapOf(A_ROOM_ID to null)
|
||||||
|
|
||||||
|
val result = missingEventRoomInvitation.toNotifications(MY_USER_ID)
|
||||||
|
|
||||||
|
result shouldBeEqualTo listOf(OneShotNotification.Removed(
|
||||||
|
key = A_ROOM_ID
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a simple event when mapping to notification then is Append`() = testWith(notificationFactory) {
|
||||||
|
val expectedNotification = notificationUtils.givenBuildSimpleInvitationNotificationFor(A_SIMPLE_EVENT, MY_USER_ID)
|
||||||
|
val roomInvitation = mapOf(AN_EVENT_ID to A_SIMPLE_EVENT)
|
||||||
|
|
||||||
|
val result = roomInvitation.toNotifications(MY_USER_ID)
|
||||||
|
|
||||||
|
result shouldBeEqualTo listOf(OneShotNotification.Append(
|
||||||
|
notification = expectedNotification,
|
||||||
|
meta = OneShotNotification.Append.Meta(
|
||||||
|
key = AN_EVENT_ID,
|
||||||
|
summaryLine = A_SIMPLE_EVENT.description,
|
||||||
|
isNoisy = A_SIMPLE_EVENT.noisy
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a missing simple event when mapping to notification then is Removed`() = testWith(notificationFactory) {
|
||||||
|
val missingEventRoomInvitation: Map<String, SimpleNotifiableEvent?> = mapOf(AN_EVENT_ID to null)
|
||||||
|
|
||||||
|
val result = missingEventRoomInvitation.toNotifications(MY_USER_ID)
|
||||||
|
|
||||||
|
result shouldBeEqualTo listOf(OneShotNotification.Removed(
|
||||||
|
key = AN_EVENT_ID
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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 expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor(events, A_ROOM_ID, MY_USER_ID, MY_AVATAR_URL)
|
||||||
|
val roomWithMessage = mapOf(A_ROOM_ID to events)
|
||||||
|
|
||||||
|
val result = roomWithMessage.toNotifications(MY_USER_ID, MY_AVATAR_URL)
|
||||||
|
|
||||||
|
result shouldBeEqualTo listOf(expectedNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a room with no events to display when mapping to notification then is Empty`() = testWith(notificationFactory) {
|
||||||
|
val emptyRoom: Map<String, List<NotifiableMessageEvent>> = mapOf(A_ROOM_ID to emptyList())
|
||||||
|
|
||||||
|
val result = emptyRoom.toNotifications(MY_USER_ID, MY_AVATAR_URL)
|
||||||
|
|
||||||
|
result shouldBeEqualTo listOf(RoomNotification.EmptyRoom(
|
||||||
|
roomId = A_ROOM_ID
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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(A_MESSAGE_EVENT.copy().apply { isRedacted = true }))
|
||||||
|
|
||||||
|
val result = redactedRoom.toNotifications(MY_USER_ID, MY_AVATAR_URL)
|
||||||
|
|
||||||
|
result shouldBeEqualTo listOf(RoomNotification.EmptyRoom(
|
||||||
|
roomId = A_ROOM_ID
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> testWith(receiver: T, block: T.() -> Unit) {
|
||||||
|
receiver.block()
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 im.vector.app.test.fakes
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import im.vector.app.features.notifications.InviteNotifiableEvent
|
||||||
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
|
import im.vector.app.features.notifications.SimpleNotifiableEvent
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
|
||||||
|
class FakeNotificationUtils {
|
||||||
|
|
||||||
|
val instance = mockk<NotificationUtils>()
|
||||||
|
|
||||||
|
fun givenBuildRoomInvitationNotificationFor(event: InviteNotifiableEvent, myUserId: String): Notification {
|
||||||
|
val mockNotification = mockk<Notification>()
|
||||||
|
every { instance.buildRoomInvitationNotification(event, myUserId) } returns mockNotification
|
||||||
|
return mockNotification
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenBuildSimpleInvitationNotificationFor(event: SimpleNotifiableEvent, myUserId: String): Notification {
|
||||||
|
val mockNotification = mockk<Notification>()
|
||||||
|
every { instance.buildSimpleEventNotification(event, myUserId) } returns mockNotification
|
||||||
|
return mockNotification
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 im.vector.app.test.fakes
|
||||||
|
|
||||||
|
import im.vector.app.features.notifications.NotifiableMessageEvent
|
||||||
|
import im.vector.app.features.notifications.RoomGroupMessageCreator
|
||||||
|
import im.vector.app.features.notifications.RoomNotification
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
|
||||||
|
class FakeRoomGroupMessageCreator {
|
||||||
|
|
||||||
|
val instance = mockk<RoomGroupMessageCreator>()
|
||||||
|
|
||||||
|
fun givenCreatesRoomMessageFor(events: List<NotifiableMessageEvent>,
|
||||||
|
roomId: String,
|
||||||
|
userDisplayName: String,
|
||||||
|
userAvatarUrl: String?): RoomNotification.Message {
|
||||||
|
val mockMessage = mockk<RoomNotification.Message>()
|
||||||
|
every { instance.createRoomMessage(events, roomId, userDisplayName, userAvatarUrl) } returns mockMessage
|
||||||
|
return mockMessage
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 im.vector.app.test.fakes
|
||||||
|
|
||||||
|
import im.vector.app.features.notifications.SummaryGroupMessageCreator
|
||||||
|
import io.mockk.mockk
|
||||||
|
|
||||||
|
class FakeSummaryGroupMessageCreator {
|
||||||
|
|
||||||
|
val instance = mockk<SummaryGroupMessageCreator>()
|
||||||
|
}
|
Loading…
Reference in New Issue