Let notifications use avatar fallback.
Extract code which handles Matrix image to its own api / impl / test modules.
This commit is contained in:
parent
8603d54778
commit
573767aca1
42 changed files with 410 additions and 194 deletions
|
|
@ -9,6 +9,7 @@
|
|||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
|
|
@ -19,9 +20,11 @@ import coil3.toBitmap
|
|||
import coil3.transform.CircleCropTransformation
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL
|
||||
import io.element.android.libraries.matrix.ui.media.InitialsAvatarBitmapGenerator
|
||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
|
|
@ -31,48 +34,71 @@ import timber.log.Timber
|
|||
class DefaultNotificationBitmapLoader(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val sdkIntProvider: BuildVersionSdkIntProvider,
|
||||
private val initialsAvatarBitmapGenerator: InitialsAvatarBitmapGenerator,
|
||||
) : NotificationBitmapLoader {
|
||||
override suspend fun getRoomBitmap(path: String?, imageLoader: ImageLoader, targetSize: Long): Bitmap? {
|
||||
if (path == null) {
|
||||
return null
|
||||
}
|
||||
return loadRoomBitmap(path, imageLoader, targetSize)
|
||||
}
|
||||
|
||||
private suspend fun loadRoomBitmap(path: String, imageLoader: ImageLoader, targetSize: Long): Bitmap? {
|
||||
override suspend fun getRoomBitmap(
|
||||
avatarData: AvatarData,
|
||||
imageLoader: ImageLoader,
|
||||
targetSize: Long,
|
||||
): Bitmap? {
|
||||
return try {
|
||||
val imageRequest = ImageRequest.Builder(context)
|
||||
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(targetSize)))
|
||||
.transformations(CircleCropTransformation())
|
||||
.build()
|
||||
val result = imageLoader.execute(imageRequest)
|
||||
result.image?.toBitmap()
|
||||
loadBitmap(
|
||||
avatarData = avatarData,
|
||||
imageLoader = imageLoader,
|
||||
targetSize = targetSize,
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Unable to load room bitmap")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUserIcon(path: String?, imageLoader: ImageLoader): IconCompat? {
|
||||
if (path == null || sdkIntProvider.get() < Build.VERSION_CODES.P) {
|
||||
override suspend fun getUserIcon(
|
||||
avatarData: AvatarData,
|
||||
imageLoader: ImageLoader,
|
||||
): IconCompat? {
|
||||
if (sdkIntProvider.get() < Build.VERSION_CODES.P) {
|
||||
return null
|
||||
}
|
||||
|
||||
return loadUserIcon(path, imageLoader)
|
||||
}
|
||||
|
||||
private suspend fun loadUserIcon(path: String, imageLoader: ImageLoader): IconCompat? {
|
||||
return try {
|
||||
val imageRequest = ImageRequest.Builder(context)
|
||||
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL)))
|
||||
.transformations(CircleCropTransformation())
|
||||
.build()
|
||||
val result = imageLoader.execute(imageRequest)
|
||||
val bitmap = result.image?.toBitmap()
|
||||
return bitmap?.let { IconCompat.createWithBitmap(it) }
|
||||
loadBitmap(
|
||||
avatarData = avatarData,
|
||||
imageLoader = imageLoader,
|
||||
targetSize = AVATAR_THUMBNAIL_SIZE_IN_PIXEL,
|
||||
)
|
||||
?.let { IconCompat.createWithBitmap(it) }
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Unable to load user bitmap")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDarkTheme(): Boolean {
|
||||
return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
|
||||
private suspend fun loadBitmap(
|
||||
avatarData: AvatarData,
|
||||
imageLoader: ImageLoader,
|
||||
targetSize: Long
|
||||
): Bitmap? {
|
||||
val path = avatarData.url
|
||||
val data = if (path != null) {
|
||||
MediaRequestData(
|
||||
source = MediaSource(path),
|
||||
kind = MediaRequestData.Kind.Thumbnail(targetSize),
|
||||
)
|
||||
} else {
|
||||
initialsAvatarBitmapGenerator.generateBitmap(
|
||||
size = targetSize.toInt(),
|
||||
avatarData = avatarData,
|
||||
useDarkTheme = isDarkTheme(),
|
||||
)
|
||||
}
|
||||
val imageRequest = ImageRequest.Builder(context)
|
||||
.data(data)
|
||||
.transformations(CircleCropTransformation())
|
||||
.build()
|
||||
return imageLoader.execute(imageRequest).image?.toBitmap()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import android.graphics.Bitmap
|
|||
import coil3.ImageLoader
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
|
|
@ -90,7 +92,18 @@ class DefaultRoomGroupMessageCreator(
|
|||
imageLoader: ImageLoader,
|
||||
): Bitmap? {
|
||||
// Use the last event (most recent?)
|
||||
return events.reversed().firstNotNullOfOrNull { it.roomAvatarPath }
|
||||
?.let { bitmapLoader.getRoomBitmap(it, imageLoader) }
|
||||
val event = events.reversed().firstOrNull { it.roomAvatarPath != null }
|
||||
?: events.reversed().firstOrNull()
|
||||
return event?.let { event ->
|
||||
bitmapLoader.getRoomBitmap(
|
||||
avatarData = AvatarData(
|
||||
id = event.roomId.value,
|
||||
name = event.roomName,
|
||||
url = event.roomAvatarPath,
|
||||
size = AvatarSize.RoomDetailsHeader,
|
||||
),
|
||||
imageLoader = imageLoader,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ package io.element.android.libraries.push.impl.notifications.conversations
|
|||
|
||||
import android.content.Context
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
|
|
@ -29,7 +28,6 @@ import io.element.android.libraries.matrix.api.MatrixClientProvider
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import io.element.android.libraries.matrix.ui.media.InitialsAvatarBitmapGenerator
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService
|
||||
import io.element.android.libraries.push.impl.intent.IntentProvider
|
||||
|
|
@ -95,15 +93,16 @@ class DefaultNotificationConversationService(
|
|||
val imageLoader = imageLoaderHolder.get(client)
|
||||
|
||||
val defaultShortcutIconSize = ShortcutManagerCompat.getIconMaxWidth(context)
|
||||
val useDarkTheme = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
val icon = bitmapLoader.getRoomBitmap(
|
||||
path = roomAvatarUrl,
|
||||
avatarData = AvatarData(
|
||||
id = roomId.value,
|
||||
name = roomName,
|
||||
url = roomAvatarUrl,
|
||||
size = AvatarSize.RoomDetailsHeader,
|
||||
),
|
||||
imageLoader = imageLoader,
|
||||
targetSize = defaultShortcutIconSize.toLong()
|
||||
)?.let(IconCompat::createWithBitmap)
|
||||
?: InitialsAvatarBitmapGenerator(useDarkTheme = useDarkTheme)
|
||||
.generateBitmap(defaultShortcutIconSize, AvatarData(id = roomId.value, name = roomName, size = AvatarSize.RoomDetailsHeader))
|
||||
?.let(IconCompat::createWithAdaptiveBitmap)
|
||||
|
||||
val shortcutInfo = ShortcutInfoCompat.Builder(context, createShortcutId(sessionId, roomId))
|
||||
.setShortLabel(roomName)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,15 @@ import coil3.ImageLoader
|
|||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.R
|
||||
|
|
@ -401,7 +404,17 @@ class DefaultNotificationCreator(
|
|||
}
|
||||
Person.Builder()
|
||||
.setName(displayName.annotateForDebug(70))
|
||||
.setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath, imageLoader))
|
||||
.setIcon(
|
||||
bitmapLoader.getUserIcon(
|
||||
avatarData = AvatarData(
|
||||
id = event.senderId.value,
|
||||
name = senderName,
|
||||
url = event.senderAvatarPath,
|
||||
size = AvatarSize.UserHeader,
|
||||
),
|
||||
imageLoader = imageLoader,
|
||||
)
|
||||
)
|
||||
.setKey(key)
|
||||
.build()
|
||||
}
|
||||
|
|
@ -460,7 +473,12 @@ class DefaultNotificationCreator(
|
|||
Person.Builder()
|
||||
// Note: name cannot be empty else NotificationCompat.MessagingStyle() will crash
|
||||
.setName(user.getBestName().annotateForDebug(50))
|
||||
.setIcon(bitmapLoader.getUserIcon(user.avatarUrl, imageLoader))
|
||||
.setIcon(
|
||||
bitmapLoader.getUserIcon(
|
||||
avatarData = user.getAvatarData(AvatarSize.UserHeader),
|
||||
imageLoader = imageLoader,
|
||||
)
|
||||
)
|
||||
.setKey(user.userId.value)
|
||||
.build()
|
||||
).also {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
|||
import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL
|
||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader
|
||||
import io.element.android.libraries.matrix.ui.test.media.FakeInitialsAvatarBitmapGenerator
|
||||
import io.element.android.libraries.push.impl.notifications.factories.MARK_AS_READ_ACTION_TITLE
|
||||
import io.element.android.libraries.push.impl.notifications.factories.QUICK_REPLY_ACTION_TITLE
|
||||
import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams
|
||||
|
|
@ -232,6 +233,7 @@ fun createRoomGroupMessageCreator(
|
|||
val bitmapLoader = DefaultNotificationBitmapLoader(
|
||||
context = RuntimeEnvironment.getApplication(),
|
||||
sdkIntProvider = sdkIntProvider,
|
||||
initialsAvatarBitmapGenerator = FakeInitialsAvatarBitmapGenerator(),
|
||||
)
|
||||
return DefaultRoomGroupMessageCreator(
|
||||
notificationCreator = createNotificationCreator(bitmapLoader = bitmapLoader),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.test.A_THREAD_ID
|
|||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader
|
||||
import io.element.android.libraries.matrix.ui.test.media.FakeInitialsAvatarBitmapGenerator
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
|
||||
|
|
@ -297,7 +298,11 @@ fun createNotificationCreator(
|
|||
context: Context = RuntimeEnvironment.getApplication(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
notificationChannels: NotificationChannels = createNotificationChannels(),
|
||||
bitmapLoader: NotificationBitmapLoader = DefaultNotificationBitmapLoader(context, FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.R)),
|
||||
bitmapLoader: NotificationBitmapLoader = DefaultNotificationBitmapLoader(
|
||||
context = context,
|
||||
sdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.R),
|
||||
initialsAvatarBitmapGenerator = FakeInitialsAvatarBitmapGenerator(),
|
||||
),
|
||||
): NotificationCreator {
|
||||
return DefaultNotificationCreator(
|
||||
context = context,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue