Browse Source

Merge pull request #2001 from element-hq/feature/bma/fixNotificationAvatar

Fix notification avatar
pull/2002/head
Benoit Marty 9 months ago committed by GitHub
parent
commit
c781d4978b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt
  2. 1
      changelog.d/1991.bugfix
  3. 2
      features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/model/InviteListInviteSummary.kt
  4. 2
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt
  5. 2
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
  6. 4
      libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt
  7. 71
      libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderHolder.kt
  8. 2
      libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/PickerProviderImpl.kt
  9. 2
      libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt
  10. 7
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt
  11. 20
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBitmapLoader.kt
  12. 3
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt
  13. 6
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt
  14. 22
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt
  15. 2
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt
  16. 24
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt
  17. 7
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt
  18. 39
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreatorTest.kt
  19. 55
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeImageLoader.kt
  20. 16
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeImageLoaderHolder.kt
  21. 2
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt
  22. 1
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt

7
appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt

@ -33,13 +33,12 @@ import dagger.assisted.AssistedInject @@ -33,13 +33,12 @@ import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.appnav.di.SessionComponentFactory
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.ui.di.MatrixUIBindings
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
import kotlinx.parcelize.Parcelize
/**
@ -52,6 +51,7 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor( @@ -52,6 +51,7 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
sessionComponentFactory: SessionComponentFactory,
private val imageLoaderHolder: ImageLoaderHolder,
) : ParentNode<LoggedInAppScopeFlowNode.NavTarget>(
navModel = PermanentNavModel(
navTargets = setOf(NavTarget),
@ -78,8 +78,7 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor( @@ -78,8 +78,7 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor(
super.onBuilt()
lifecycle.subscribe(
onCreate = {
val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory)
Coil.setImageLoader(imageLoaderHolder.get(inputs.matrixClient))
},
)
}

1
changelog.d/1991.bugfix

@ -0,0 +1 @@ @@ -0,0 +1 @@
Fix avatar not displayed in notification when the app is not in background

2
features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/model/InviteListInviteSummary.kt

@ -33,7 +33,7 @@ data class InviteListInviteSummary( @@ -33,7 +33,7 @@ data class InviteListInviteSummary(
val isNew: Boolean = false,
)
data class InviteSender constructor(
data class InviteSender(
val userId: UserId,
val displayName: String,
val avatarData: AvatarData = AvatarData(userId.value, displayName, size = AvatarSize.InviteSender),

2
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
package io.element.android.features.login.impl.resolver
data class HomeserverData constructor(
data class HomeserverData(
// The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url
val homeserverUrl: String,
// True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid

2
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt

@ -91,7 +91,7 @@ import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility @@ -91,7 +91,7 @@ import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility
import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService
@OptIn(ExperimentalCoroutinesApi::class)
class RustMatrixClient constructor(
class RustMatrixClient(
private val client: Client,
private val syncService: ClientSyncService,
private val sessionStore: SessionStore,

4
libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt

@ -28,8 +28,8 @@ import okhttp3.OkHttpClient @@ -28,8 +28,8 @@ import okhttp3.OkHttpClient
import javax.inject.Inject
import javax.inject.Provider
class LoggedInImageLoaderFactory @Inject constructor(
@ApplicationContext private val context: Context,
class LoggedInImageLoaderFactory(
private val context: Context,
private val matrixClient: MatrixClient,
private val okHttpClient: Provider<OkHttpClient>,
) : ImageLoaderFactory {

71
libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderHolder.kt

@ -0,0 +1,71 @@ @@ -0,0 +1,71 @@
/*
* 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.ui.media
import android.content.Context
import coil.ImageLoader
import com.squareup.anvil.annotations.ContributesBinding
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.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
import okhttp3.OkHttpClient
import javax.inject.Inject
import javax.inject.Provider
interface ImageLoaderHolder {
fun get(client: MatrixClient): ImageLoader
}
@ContributesBinding(AppScope::class)
@SingleIn(AppScope::class)
class DefaultImageLoaderHolder @Inject constructor(
@ApplicationContext private val context: Context,
private val okHttpClient: Provider<OkHttpClient>,
private val sessionObserver: SessionObserver,
) : ImageLoaderHolder {
private val map = mutableMapOf<SessionId, ImageLoader>()
init {
observeSessions()
}
private fun observeSessions() {
sessionObserver.addListener(object : SessionListener {
override suspend fun onSessionCreated(userId: String) = Unit
override suspend fun onSessionDeleted(userId: String) {
map.remove(SessionId(userId))
}
})
}
override fun get(client: MatrixClient): ImageLoader {
return synchronized(map) {
map.getOrPut(client.sessionId) {
LoggedInImageLoaderFactory(
context = context,
matrixClient = client,
okHttpClient = okHttpClient,
).newImageLoader()
}
}
}
}

2
libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/PickerProviderImpl.kt

@ -37,7 +37,7 @@ import java.util.UUID @@ -37,7 +37,7 @@ import java.util.UUID
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class PickerProviderImpl constructor(private val isInTest: Boolean) : PickerProvider {
class PickerProviderImpl(private val isInTest: Boolean) : PickerProvider {
@Inject
constructor(): this(false)

2
libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt

@ -27,7 +27,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi @@ -27,7 +27,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.PermissionStatus
class FakeComposablePermissionStateProvider constructor(
class FakeComposablePermissionStateProvider(
private val permissionState: FakePermissionState
) : ComposablePermissionStateProvider {
private lateinit var onPermissionResult: (Boolean) -> Unit

7
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt

@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.core.RoomId @@ -30,6 +30,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.ThreadId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.services.appnavstate.api.AppNavigationStateService
@ -61,6 +62,7 @@ class DefaultNotificationDrawerManager @Inject constructor( @@ -61,6 +62,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
private val dispatchers: CoroutineDispatchers,
private val buildMeta: BuildMeta,
private val matrixClientProvider: MatrixClientProvider,
private val imageLoaderHolder: ImageLoaderHolder,
) : NotificationDrawerManager {
private var appNavigationStateObserver: Job? = null
@ -288,10 +290,11 @@ class DefaultNotificationDrawerManager @Inject constructor( @@ -288,10 +290,11 @@ class DefaultNotificationDrawerManager @Inject constructor(
}
eventsForSessions.forEach { (sessionId, notifiableEvents) ->
val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow()
val imageLoader = imageLoaderHolder.get(client)
val currentUser = tryOrNull(
onError = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") },
operation = {
val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow()
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
val myUserDisplayName = client.loadUserDisplayName().getOrNull() ?: sessionId.value
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
@ -307,7 +310,7 @@ class DefaultNotificationDrawerManager @Inject constructor( @@ -307,7 +310,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
avatarUrl = null
)
notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents)
notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents, imageLoader)
}
}
}

20
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBitmapLoader.kt

@ -21,7 +21,7 @@ import android.graphics.Bitmap @@ -21,7 +21,7 @@ import android.graphics.Bitmap
import android.os.Build
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import coil.imageLoader
import coil.ImageLoader
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import io.element.android.libraries.di.ApplicationContext
@ -39,21 +39,22 @@ class NotificationBitmapLoader @Inject constructor( @@ -39,21 +39,22 @@ class NotificationBitmapLoader @Inject constructor(
/**
* Get icon of a room.
* @param path mxc url
* @param imageLoader Coil image loader
*/
suspend fun getRoomBitmap(path: String?): Bitmap? {
suspend fun getRoomBitmap(path: String?, imageLoader: ImageLoader): Bitmap? {
if (path == null) {
return null
}
return loadRoomBitmap(path)
return loadRoomBitmap(path, imageLoader)
}
private suspend fun loadRoomBitmap(path: String): Bitmap? {
private suspend fun loadRoomBitmap(path: String, imageLoader: ImageLoader): Bitmap? {
return try {
val imageRequest = ImageRequest.Builder(context)
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024)))
.transformations(CircleCropTransformation())
.build()
val result = context.imageLoader.execute(imageRequest)
val result = imageLoader.execute(imageRequest)
result.drawable?.toBitmap()
} catch (e: Throwable) {
Timber.e(e, "Unable to load room bitmap")
@ -65,22 +66,23 @@ class NotificationBitmapLoader @Inject constructor( @@ -65,22 +66,23 @@ class NotificationBitmapLoader @Inject constructor(
* Get icon of a user.
* Before Android P, this does nothing because the icon won't be used
* @param path mxc url
* @param imageLoader Coil image loader
*/
suspend fun getUserIcon(path: String?): IconCompat? {
suspend fun getUserIcon(path: String?, imageLoader: ImageLoader): IconCompat? {
if (path == null || sdkIntProvider.get() < Build.VERSION_CODES.P) {
return null
}
return loadUserIcon(path)
return loadUserIcon(path, imageLoader)
}
private suspend fun loadUserIcon(path: String): IconCompat? {
private suspend fun loadUserIcon(path: String, imageLoader: ImageLoader): IconCompat? {
return try {
val imageRequest = ImageRequest.Builder(context)
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024)))
.transformations(CircleCropTransformation())
.build()
val result = context.imageLoader.execute(imageRequest)
val result = imageLoader.execute(imageRequest)
val bitmap = result.drawable?.toBitmap()
return bitmap?.let { IconCompat.createWithBitmap(it) }
} catch (e: Throwable) {

3
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package io.element.android.libraries.push.impl.notifications
import android.app.Notification
import coil.ImageLoader
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
@ -36,6 +37,7 @@ class NotificationFactory @Inject constructor( @@ -36,6 +37,7 @@ class NotificationFactory @Inject constructor(
suspend fun Map<RoomId, ProcessedMessageEvents>.toNotifications(
currentUser: MatrixUser,
imageLoader: ImageLoader,
): List<RoomNotification> {
return map { (roomId, events) ->
when {
@ -46,6 +48,7 @@ class NotificationFactory @Inject constructor( @@ -46,6 +48,7 @@ class NotificationFactory @Inject constructor(
currentUser = currentUser,
events = messageEvents,
roomId = roomId,
imageLoader = imageLoader,
)
}
}

6
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package io.element.android.libraries.push.impl.notifications
import coil.ImageLoader
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.user.MatrixUser
@ -38,11 +39,12 @@ class NotificationRenderer @Inject constructor( @@ -38,11 +39,12 @@ class NotificationRenderer @Inject constructor(
suspend fun render(
currentUser: MatrixUser,
useCompleteNotificationFormat: Boolean,
eventsToProcess: List<ProcessedEvent<NotifiableEvent>>
eventsToProcess: List<ProcessedEvent<NotifiableEvent>>,
imageLoader: ImageLoader,
) {
val groupedEvents = eventsToProcess.groupByType()
with(notificationFactory) {
val roomNotifications = groupedEvents.roomEvents.toNotifications(currentUser)
val roomNotifications = groupedEvents.roomEvents.toNotifications(currentUser, imageLoader)
val invitationNotifications = groupedEvents.invitationEvents.toNotifications()
val simpleNotifications = groupedEvents.simpleEvents.toNotifications()
val fallbackNotifications = groupedEvents.fallbackEvents.toNotifications()

22
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt

@ -23,6 +23,7 @@ import androidx.core.app.NotificationCompat @@ -23,6 +23,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.Person
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import coil.ImageLoader
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.push.impl.R
@ -42,6 +43,7 @@ class RoomGroupMessageCreator @Inject constructor( @@ -42,6 +43,7 @@ class RoomGroupMessageCreator @Inject constructor(
currentUser: MatrixUser,
events: List<NotifiableMessageEvent>,
roomId: RoomId,
imageLoader: ImageLoader,
): RoomNotification.Message {
val lastKnownRoomEvent = events.last()
val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderName ?: "Room name (${roomId.value.take(8)}…)"
@ -49,13 +51,13 @@ class RoomGroupMessageCreator @Inject constructor( @@ -49,13 +51,13 @@ class RoomGroupMessageCreator @Inject constructor(
val style = NotificationCompat.MessagingStyle(
Person.Builder()
.setName(currentUser.displayName?.annotateForDebug(50))
.setIcon(bitmapLoader.getUserIcon(currentUser.avatarUrl))
.setIcon(bitmapLoader.getUserIcon(currentUser.avatarUrl, imageLoader))
.setKey(lastKnownRoomEvent.sessionId.value)
.build()
).also {
it.conversationTitle = roomName.takeIf { roomIsGroup }?.annotateForDebug(51)
it.isGroupConversation = roomIsGroup
it.addMessagesFromEvents(events)
it.addMessagesFromEvents(events, imageLoader)
}
val tickerText = if (roomIsGroup) {
@ -64,7 +66,7 @@ class RoomGroupMessageCreator @Inject constructor( @@ -64,7 +66,7 @@ class RoomGroupMessageCreator @Inject constructor(
stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description)
}
val largeBitmap = getRoomBitmap(events)
val largeBitmap = getRoomBitmap(events, imageLoader)
val lastMessageTimestamp = events.last().timestamp
val smartReplyErrors = events.filter { it.isSmartReplyError() }
@ -98,14 +100,17 @@ class RoomGroupMessageCreator @Inject constructor( @@ -98,14 +100,17 @@ class RoomGroupMessageCreator @Inject constructor(
)
}
private suspend fun NotificationCompat.MessagingStyle.addMessagesFromEvents(events: List<NotifiableMessageEvent>) {
private suspend fun NotificationCompat.MessagingStyle.addMessagesFromEvents(
events: List<NotifiableMessageEvent>,
imageLoader: ImageLoader,
) {
events.forEach { event ->
val senderPerson = if (event.outGoingMessage) {
null
} else {
Person.Builder()
.setName(event.senderName?.annotateForDebug(70))
.setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath))
.setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath, imageLoader))
.setKey(event.senderId.value)
.build()
}
@ -167,10 +172,13 @@ class RoomGroupMessageCreator @Inject constructor( @@ -167,10 +172,13 @@ class RoomGroupMessageCreator @Inject constructor(
}
}
private suspend fun getRoomBitmap(events: List<NotifiableMessageEvent>): Bitmap? {
private suspend fun getRoomBitmap(
events: List<NotifiableMessageEvent>,
imageLoader: ImageLoader,
): Bitmap? {
// Use the last event (most recent?)
return events.reversed().firstNotNullOfOrNull { it.roomAvatarPath }
?.let { bitmapLoader.getRoomBitmap(it) }
?.let { bitmapLoader.getRoomBitmap(it, imageLoader) }
}
}

2
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt

@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.test.A_THREAD_ID @@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.test.A_THREAD_ID
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.push.impl.notifications.fake.FakeAndroidNotificationFactory
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoaderHolder
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
@ -129,6 +130,7 @@ class DefaultNotificationDrawerManagerTest { @@ -129,6 +130,7 @@ class DefaultNotificationDrawerManagerTest {
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
buildMeta = aBuildMeta(),
matrixClientProvider = FakeMatrixClientProvider(),
imageLoaderHolder = FakeImageLoaderHolder(),
)
}
}

24
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt

@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -23,6 +23,7 @@ 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_SESSION_ID
import io.element.android.libraries.push.impl.notifications.fake.FakeAndroidNotificationFactory
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
@ -30,12 +31,15 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNoti @@ -30,12 +31,15 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNoti
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
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)
@RunWith(RobolectricTestRunner::class)
class NotificationFactoryTest {
private val androidNotificationFactory = FakeAndroidNotificationFactory()
@ -130,11 +134,14 @@ class NotificationFactoryTest { @@ -130,11 +134,14 @@ class NotificationFactoryTest {
)
val roomWithMessage = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT)))
val fakeImageLoader = FakeImageLoader()
val result = roomWithMessage.toNotifications(
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL),
imageLoader = fakeImageLoader.getImageLoader(),
)
assertThat(result).isEqualTo(listOf(expectedNotification))
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
@Test
@ -142,8 +149,10 @@ class NotificationFactoryTest { @@ -142,8 +149,10 @@ class NotificationFactoryTest {
val events = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, A_MESSAGE_EVENT))
val emptyRoom = mapOf(A_ROOM_ID to events)
val fakeImageLoader = FakeImageLoader()
val result = emptyRoom.toNotifications(
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL),
imageLoader = fakeImageLoader.getImageLoader(),
)
assertThat(result).isEqualTo(
@ -153,14 +162,17 @@ class NotificationFactoryTest { @@ -153,14 +162,17 @@ class NotificationFactoryTest {
)
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
@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(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(isRedacted = true))))
val fakeImageLoader = FakeImageLoader()
val result = redactedRoom.toNotifications(
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL),
imageLoader = fakeImageLoader.getImageLoader(),
)
assertThat(result).isEqualTo(
@ -170,6 +182,7 @@ class NotificationFactoryTest { @@ -170,6 +182,7 @@ class NotificationFactoryTest {
)
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
@Test
@ -189,11 +202,14 @@ class NotificationFactoryTest { @@ -189,11 +202,14 @@ class NotificationFactoryTest {
A_ROOM_ID,
)
val fakeImageLoader = FakeImageLoader()
val result = roomWithRedactedMessage.toNotifications(
MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL),
imageLoader = fakeImageLoader.getImageLoader(),
)
assertThat(result).isEqualTo(listOf(expectedNotification))
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
}

7
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt

@ -21,12 +21,15 @@ import io.element.android.libraries.matrix.api.user.MatrixUser @@ -21,12 +21,15 @@ 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.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationFactory
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
private const val MY_USER_DISPLAY_NAME = "display-name"
private const val MY_USER_AVATAR_URL = "avatar-url"
@ -42,6 +45,7 @@ private val MESSAGE_META = RoomNotification.Message.Meta( @@ -42,6 +45,7 @@ private val MESSAGE_META = RoomNotification.Message.Meta(
)
private val ONE_SHOT_META = OneShotNotification.Append.Meta(key = "ignored", summaryLine = "ignored", isNoisy = false, timestamp = -1)
@RunWith(RobolectricTestRunner::class)
class NotificationRendererTest {
private val notificationDisplayer = FakeNotificationDisplayer()
@ -197,7 +201,8 @@ class NotificationRendererTest { @@ -197,7 +201,8 @@ class NotificationRendererTest {
notificationRenderer.render(
MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL),
useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT,
eventsToProcess = AN_EVENT_LIST
eventsToProcess = AN_EVENT_LIST,
imageLoader = FakeImageLoader().getImageLoader(),
)
}

39
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreatorTest.kt

@ -17,19 +17,15 @@ @@ -17,19 +17,15 @@
package io.element.android.libraries.push.impl.notifications
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Build
import coil.Coil
import coil.ImageLoader
import coil.annotation.ExperimentalCoilApi
import coil.test.FakeImageLoaderEngine
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.media.MediaRequestData
import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
@ -50,6 +46,7 @@ class RoomGroupMessageCreatorTest { @@ -50,6 +46,7 @@ class RoomGroupMessageCreatorTest {
@Test
fun `test createRoomMessage with one Event`() = runTest {
val sut = createRoomGroupMessageCreator()
val fakeImageLoader = FakeImageLoader()
val result = sut.createRoomMessage(
currentUser = aMatrixUser(),
events = listOf(
@ -58,6 +55,7 @@ class RoomGroupMessageCreatorTest { @@ -58,6 +55,7 @@ class RoomGroupMessageCreatorTest {
)
),
roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(),
)
val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString()
@ -71,11 +69,13 @@ class RoomGroupMessageCreatorTest { @@ -71,11 +69,13 @@ class RoomGroupMessageCreatorTest {
shouldBing = false,
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
@Test
fun `test createRoomMessage with one noisy Event`() = runTest {
val sut = createRoomGroupMessageCreator()
val fakeImageLoader = FakeImageLoader()
val result = sut.createRoomMessage(
currentUser = aMatrixUser(),
events = listOf(
@ -84,6 +84,7 @@ class RoomGroupMessageCreatorTest { @@ -84,6 +84,7 @@ class RoomGroupMessageCreatorTest {
)
),
roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(),
)
val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString()
@ -97,6 +98,7 @@ class RoomGroupMessageCreatorTest { @@ -97,6 +98,7 @@ class RoomGroupMessageCreatorTest {
shouldBing = true,
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
@Test
@ -141,20 +143,7 @@ class RoomGroupMessageCreatorTest { @@ -141,20 +143,7 @@ class RoomGroupMessageCreatorTest {
api: Int,
expectedCoilRequests: List<Any>,
) = runTest {
val coilRequests = mutableListOf<Any>()
val engine = FakeImageLoaderEngine.Builder()
.intercept(
predicate = {
coilRequests.add(it)
true
},
drawable = ColorDrawable(Color.BLUE)
)
.build()
val imageLoader = ImageLoader.Builder(RuntimeEnvironment.getApplication())
.components { add(engine) }
.build()
Coil.setImageLoader(imageLoader)
val fakeImageLoader = FakeImageLoader()
val sut = createRoomGroupMessageCreator(
sdkIntProvider = FakeBuildVersionSdkIntProvider(api)
)
@ -170,6 +159,7 @@ class RoomGroupMessageCreatorTest { @@ -170,6 +159,7 @@ class RoomGroupMessageCreatorTest {
)
),
roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(),
)
val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString()
@ -183,12 +173,13 @@ class RoomGroupMessageCreatorTest { @@ -183,12 +173,13 @@ class RoomGroupMessageCreatorTest {
shouldBing = false,
)
)
assertThat(coilRequests.toList()).isEqualTo(expectedCoilRequests)
assertThat(fakeImageLoader.getCoilRequests()).isEqualTo(expectedCoilRequests)
}
@Test
fun `test createRoomMessage with two Events`() = runTest {
val sut = createRoomGroupMessageCreator()
val fakeImageLoader = FakeImageLoader()
val result = sut.createRoomMessage(
currentUser = aMatrixUser(),
events = listOf(
@ -196,6 +187,7 @@ class RoomGroupMessageCreatorTest { @@ -196,6 +187,7 @@ class RoomGroupMessageCreatorTest {
aNotifiableMessageEvent(timestamp = A_TIMESTAMP + 10),
),
roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(),
)
val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString()
@ -209,11 +201,13 @@ class RoomGroupMessageCreatorTest { @@ -209,11 +201,13 @@ class RoomGroupMessageCreatorTest {
shouldBing = false,
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
@Test
fun `test createRoomMessage with smart reply error`() = runTest {
val sut = createRoomGroupMessageCreator()
val fakeImageLoader = FakeImageLoader()
val result = sut.createRoomMessage(
currentUser = aMatrixUser(),
events = listOf(
@ -223,6 +217,7 @@ class RoomGroupMessageCreatorTest { @@ -223,6 +217,7 @@ class RoomGroupMessageCreatorTest {
),
),
roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(),
)
val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString()
@ -236,11 +231,13 @@ class RoomGroupMessageCreatorTest { @@ -236,11 +231,13 @@ class RoomGroupMessageCreatorTest {
shouldBing = false,
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
@Test
fun `test createRoomMessage for direct room`() = runTest {
val sut = createRoomGroupMessageCreator()
val fakeImageLoader = FakeImageLoader()
val result = sut.createRoomMessage(
currentUser = aMatrixUser(),
events = listOf(
@ -249,6 +246,7 @@ class RoomGroupMessageCreatorTest { @@ -249,6 +246,7 @@ class RoomGroupMessageCreatorTest {
),
),
roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(),
)
val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString()
@ -262,6 +260,7 @@ class RoomGroupMessageCreatorTest { @@ -262,6 +260,7 @@ class RoomGroupMessageCreatorTest {
shouldBing = false,
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
}

55
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeImageLoader.kt

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
/*
* 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.fake
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import coil.ImageLoader
import coil.annotation.ExperimentalCoilApi
import coil.test.FakeImageLoaderEngine
import org.robolectric.RuntimeEnvironment
@OptIn(ExperimentalCoilApi::class)
class FakeImageLoader {
private val coilRequests = mutableListOf<Any>()
private var cache: ImageLoader? = null
fun getImageLoader(): ImageLoader {
return cache ?: ImageLoader.Builder(RuntimeEnvironment.getApplication())
.components {
val engine = FakeImageLoaderEngine.Builder()
.intercept(
predicate = {
coilRequests.add(it)
true
},
drawable = ColorDrawable(Color.BLUE)
)
.build()
add(engine)
}
.build()
.also {
cache = it
}
}
fun getCoilRequests(): List<Any> {
return coilRequests.toList()
}
}

16
libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/di/MatrixUIBindings.kt → libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeImageLoaderHolder.kt

@ -14,13 +14,15 @@ @@ -14,13 +14,15 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.ui.di
package io.element.android.libraries.push.impl.notifications.fake
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.ui.media.LoggedInImageLoaderFactory
import coil.ImageLoader
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
@ContributesTo(SessionScope::class)
interface MatrixUIBindings {
fun loggedInImageLoaderFactory(): LoggedInImageLoaderFactory
class FakeImageLoaderHolder : ImageLoaderHolder {
private val fakeImageLoader = FakeImageLoader()
override fun get(client: MatrixClient): ImageLoader {
return fakeImageLoader.getImageLoader()
}
}

2
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt

@ -40,7 +40,7 @@ class FakeNotificationFactory { @@ -40,7 +40,7 @@ class FakeNotificationFactory {
summaryNotification: SummaryNotification
) {
with(instance) {
coEvery { groupedEvents.roomEvents.toNotifications(matrixUser) } returns roomNotifications
coEvery { groupedEvents.roomEvents.toNotifications(matrixUser, any()) } returns roomNotifications
every { groupedEvents.invitationEvents.toNotifications() } returns invitationNotifications
every { groupedEvents.simpleEvents.toNotifications() } returns simpleNotifications
every { groupedEvents.fallbackEvents.toNotifications() } returns fallbackNotifications

1
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt

@ -39,6 +39,7 @@ class FakeRoomGroupMessageCreator { @@ -39,6 +39,7 @@ class FakeRoomGroupMessageCreator {
currentUser = matrixUser,
events = events,
roomId = roomId,
imageLoader = any(),
)
} returns mockMessage
return mockMessage

Loading…
Cancel
Save