Load avatar for notification when there is no active imageLoader. (#1991)

This commit is contained in:
Benoit Marty
2023-12-12 15:28:53 +01:00
committed by Benoit Marty
parent b0872d9b58
commit 0ccfa2a15b
17 changed files with 263 additions and 39 deletions

View File

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

View File

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

View File

@@ -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()
}
}
}
}

View File

@@ -61,6 +61,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val matrixClientProvider: MatrixClientProvider, private val matrixClientProvider: MatrixClientProvider,
private val imageLoaderHolder: ImageLoaderHolder,
) : NotificationDrawerManager { ) : NotificationDrawerManager {
private var appNavigationStateObserver: Job? = null private var appNavigationStateObserver: Job? = null
@@ -288,10 +289,11 @@ class DefaultNotificationDrawerManager @Inject constructor(
} }
eventsForSessions.forEach { (sessionId, notifiableEvents) -> eventsForSessions.forEach { (sessionId, notifiableEvents) ->
val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow()
val imageLoader = imageLoaderHolder.get(client)
val currentUser = tryOrNull( val currentUser = tryOrNull(
onError = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") }, onError = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") },
operation = { operation = {
val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow()
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash // myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
val myUserDisplayName = client.loadUserDisplayName().getOrNull() ?: sessionId.value val myUserDisplayName = client.loadUserDisplayName().getOrNull() ?: sessionId.value
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull() val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
@@ -307,7 +309,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
avatarUrl = null avatarUrl = null
) )
notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents) notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents, imageLoader)
} }
} }
} }

View File

@@ -0,0 +1,72 @@
/*
* 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
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.matrix.ui.media.LoggedInImageLoaderFactory
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()
}
}
}
}

View File

@@ -21,7 +21,7 @@ import android.graphics.Bitmap
import android.os.Build import android.os.Build
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import coil.imageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
@@ -39,21 +39,22 @@ class NotificationBitmapLoader @Inject constructor(
/** /**
* Get icon of a room. * Get icon of a room.
* @param path mxc url * @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) { if (path == null) {
return 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 { return try {
val imageRequest = ImageRequest.Builder(context) val imageRequest = ImageRequest.Builder(context)
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024))) .data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024)))
.transformations(CircleCropTransformation()) .transformations(CircleCropTransformation())
.build() .build()
val result = context.imageLoader.execute(imageRequest) val result = imageLoader.execute(imageRequest)
result.drawable?.toBitmap() result.drawable?.toBitmap()
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e, "Unable to load room bitmap") Timber.e(e, "Unable to load room bitmap")
@@ -65,22 +66,23 @@ 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 * @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) { if (path == null || sdkIntProvider.get() < Build.VERSION_CODES.P) {
return null 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 { return try {
val imageRequest = ImageRequest.Builder(context) val imageRequest = ImageRequest.Builder(context)
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024))) .data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024)))
.transformations(CircleCropTransformation()) .transformations(CircleCropTransformation())
.build() .build()
val result = context.imageLoader.execute(imageRequest) val result = imageLoader.execute(imageRequest)
val bitmap = result.drawable?.toBitmap() val bitmap = result.drawable?.toBitmap()
return bitmap?.let { IconCompat.createWithBitmap(it) } return bitmap?.let { IconCompat.createWithBitmap(it) }
} catch (e: Throwable) { } catch (e: Throwable) {

View File

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

View File

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

View File

@@ -23,6 +23,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.Person import androidx.core.app.Person
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans import androidx.core.text.inSpans
import coil.ImageLoader
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.user.MatrixUser 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
@@ -42,6 +43,7 @@ class RoomGroupMessageCreator @Inject constructor(
currentUser: MatrixUser, currentUser: MatrixUser,
events: List<NotifiableMessageEvent>, events: List<NotifiableMessageEvent>,
roomId: RoomId, roomId: RoomId,
imageLoader: ImageLoader,
): 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)}…)"
@@ -49,13 +51,13 @@ class RoomGroupMessageCreator @Inject constructor(
val style = NotificationCompat.MessagingStyle( val style = NotificationCompat.MessagingStyle(
Person.Builder() Person.Builder()
.setName(currentUser.displayName?.annotateForDebug(50)) .setName(currentUser.displayName?.annotateForDebug(50))
.setIcon(bitmapLoader.getUserIcon(currentUser.avatarUrl)) .setIcon(bitmapLoader.getUserIcon(currentUser.avatarUrl, imageLoader))
.setKey(lastKnownRoomEvent.sessionId.value) .setKey(lastKnownRoomEvent.sessionId.value)
.build() .build()
).also { ).also {
it.conversationTitle = roomName.takeIf { roomIsGroup }?.annotateForDebug(51) it.conversationTitle = roomName.takeIf { roomIsGroup }?.annotateForDebug(51)
it.isGroupConversation = roomIsGroup it.isGroupConversation = roomIsGroup
it.addMessagesFromEvents(events) it.addMessagesFromEvents(events, imageLoader)
} }
val tickerText = if (roomIsGroup) { val tickerText = if (roomIsGroup) {
@@ -64,7 +66,7 @@ class RoomGroupMessageCreator @Inject constructor(
stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description) 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 lastMessageTimestamp = events.last().timestamp
val smartReplyErrors = events.filter { it.isSmartReplyError() } val smartReplyErrors = events.filter { it.isSmartReplyError() }
@@ -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 -> events.forEach { event ->
val senderPerson = if (event.outGoingMessage) { val senderPerson = if (event.outGoingMessage) {
null null
} else { } else {
Person.Builder() Person.Builder()
.setName(event.senderName?.annotateForDebug(70)) .setName(event.senderName?.annotateForDebug(70))
.setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath)) .setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath, imageLoader))
.setKey(event.senderId.value) .setKey(event.senderId.value)
.build() .build()
} }
@@ -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?) // Use the last event (most recent?)
return events.reversed().firstNotNullOfOrNull { it.roomAvatarPath } return events.reversed().firstNotNullOfOrNull { it.roomAvatarPath }
?.let { bitmapLoader.getRoomBitmap(it) } ?.let { bitmapLoader.getRoomBitmap(it, imageLoader) }
} }
} }

View File

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

View File

@@ -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_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_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.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.FakeRoomGroupMessageCreator
import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
@@ -131,7 +132,8 @@ class NotificationFactoryTest {
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( 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(),
) )
assertThat(result).isEqualTo(listOf(expectedNotification)) assertThat(result).isEqualTo(listOf(expectedNotification))
@@ -143,7 +145,8 @@ class NotificationFactoryTest {
val emptyRoom = mapOf(A_ROOM_ID to events) val emptyRoom = mapOf(A_ROOM_ID to events)
val result = emptyRoom.toNotifications( 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(),
) )
assertThat(result).isEqualTo( assertThat(result).isEqualTo(
@@ -160,7 +163,8 @@ class NotificationFactoryTest {
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( 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(),
) )
assertThat(result).isEqualTo( assertThat(result).isEqualTo(
@@ -190,7 +194,8 @@ class NotificationFactoryTest {
) )
val result = roomWithRedactedMessage.toNotifications( 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(),
) )
assertThat(result).isEqualTo(listOf(expectedNotification)) assertThat(result).isEqualTo(listOf(expectedNotification))

View File

@@ -21,6 +21,7 @@ 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
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.FakeNotificationDisplayer
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
@@ -197,7 +198,8 @@ class NotificationRendererTest {
notificationRenderer.render( notificationRenderer.render(
MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL), MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL),
useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT, useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT,
eventsToProcess = AN_EVENT_LIST eventsToProcess = AN_EVENT_LIST,
imageLoader = FakeImageLoader(),
) )
} }

View File

@@ -30,6 +30,7 @@ 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.components.aMatrixUser
import io.element.android.libraries.matrix.ui.media.MediaRequestData 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.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.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
@@ -58,6 +59,7 @@ class RoomGroupMessageCreatorTest {
) )
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = FakeImageLoader(),
) )
val resultMetaWithoutFormatting = result.meta.copy( val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString() summaryLine = result.meta.summaryLine.toString()
@@ -84,6 +86,7 @@ class RoomGroupMessageCreatorTest {
) )
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = FakeImageLoader(),
) )
val resultMetaWithoutFormatting = result.meta.copy( val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString() summaryLine = result.meta.summaryLine.toString()
@@ -170,6 +173,7 @@ class RoomGroupMessageCreatorTest {
) )
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = imageLoader,
) )
val resultMetaWithoutFormatting = result.meta.copy( val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString() summaryLine = result.meta.summaryLine.toString()
@@ -196,6 +200,7 @@ class RoomGroupMessageCreatorTest {
aNotifiableMessageEvent(timestamp = A_TIMESTAMP + 10), aNotifiableMessageEvent(timestamp = A_TIMESTAMP + 10),
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = FakeImageLoader(),
) )
val resultMetaWithoutFormatting = result.meta.copy( val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString() summaryLine = result.meta.summaryLine.toString()
@@ -223,6 +228,7 @@ class RoomGroupMessageCreatorTest {
), ),
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = FakeImageLoader(),
) )
val resultMetaWithoutFormatting = result.meta.copy( val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString() summaryLine = result.meta.summaryLine.toString()
@@ -249,6 +255,7 @@ class RoomGroupMessageCreatorTest {
), ),
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = FakeImageLoader(),
) )
val resultMetaWithoutFormatting = result.meta.copy( val resultMetaWithoutFormatting = result.meta.copy(
summaryLine = result.meta.summaryLine.toString() summaryLine = result.meta.summaryLine.toString()

View File

@@ -0,0 +1,47 @@
/*
* 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 coil.ComponentRegistry
import coil.ImageLoader
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil.request.DefaultRequestOptions
import coil.request.Disposable
import coil.request.ImageRequest
import coil.request.ImageResult
class FakeImageLoader : ImageLoader {
override val components: ComponentRegistry = error("Fake class")
override val defaults: DefaultRequestOptions = error("Fake class")
override val diskCache: DiskCache? = null
override val memoryCache: MemoryCache? = null
override fun enqueue(request: ImageRequest): Disposable {
error("Fake class")
}
override suspend fun execute(request: ImageRequest): ImageResult {
error("Fake class")
}
override fun newBuilder(): ImageLoader.Builder {
error("Fake class")
}
override fun shutdown() = Unit
}

View File

@@ -14,13 +14,14 @@
* limitations under the License. * 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 coil.ImageLoader
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.ui.media.LoggedInImageLoaderFactory import io.element.android.libraries.push.impl.notifications.ImageLoaderHolder
@ContributesTo(SessionScope::class) class FakeImageLoaderHolder : ImageLoaderHolder {
interface MatrixUIBindings { override fun get(client: MatrixClient): ImageLoader {
fun loggedInImageLoaderFactory(): LoggedInImageLoaderFactory return FakeImageLoader()
}
} }

View File

@@ -40,7 +40,7 @@ class FakeNotificationFactory {
summaryNotification: SummaryNotification summaryNotification: SummaryNotification
) { ) {
with(instance) { 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.invitationEvents.toNotifications() } returns invitationNotifications
every { groupedEvents.simpleEvents.toNotifications() } returns simpleNotifications every { groupedEvents.simpleEvents.toNotifications() } returns simpleNotifications
every { groupedEvents.fallbackEvents.toNotifications() } returns fallbackNotifications every { groupedEvents.fallbackEvents.toNotifications() } returns fallbackNotifications

View File

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