Load avatar for notification when there is no active imageLoader. (#1991)
This commit is contained in:
parent
764d7685c2
commit
4ec2c84241
17 changed files with 263 additions and 39 deletions
|
|
@ -61,6 +61,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 +289,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 +309,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
avatarUrl = null
|
||||
)
|
||||
|
||||
notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents)
|
||||
notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents, imageLoader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
|||
/**
|
||||
* 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(
|
|||
* 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) {
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
|
||||
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(
|
|||
currentUser = currentUser,
|
||||
events = messageEvents,
|
||||
roomId = roomId,
|
||||
imageLoader = imageLoader,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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(
|
|||
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(
|
|||
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(
|
|||
)
|
||||
}
|
||||
|
||||
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(
|
|||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue