Fix missing icon on shortcut entries

- adds locus id and context to the messages screen
- only shows shorcuts for the most recent rooms with activity (eventually this might want to be customised
This commit is contained in:
Adam Brown 2023-01-14 11:47:31 +00:00
parent 2e12e5e089
commit 9cd9520e52
10 changed files with 43 additions and 17 deletions

View File

@ -165,6 +165,7 @@ internal class FeatureModules internal constructor(
DirectoryModule( DirectoryModule(
context = context, context = context,
chatEngine = chatEngineModule.engine, chatEngine = chatEngineModule.engine,
iconLoader = imageLoaderModule.iconLoader(),
) )
} }
val loginModule by unsafeLazy { val loginModule by unsafeLazy {

View File

@ -4,5 +4,6 @@ plugins {
dependencies { dependencies {
compileOnly project(":domains:android:stub") compileOnly project(":domains:android:stub")
compileOnly libs.androidx.annotation
implementation project(":core") implementation project(":core")
} }

View File

@ -1,20 +1,30 @@
package app.dapk.st.core package app.dapk.st.core
import android.os.Build import android.os.Build
import androidx.annotation.ChecksSdkIntAtLeast
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O, lambda = 0)
fun <T> DeviceMeta.isAtLeastO(block: () -> T, fallback: () -> T = { throw IllegalStateException("not handled") }): T { fun <T> DeviceMeta.isAtLeastO(block: () -> T, fallback: () -> T = { throw IllegalStateException("not handled") }): T {
return if (this.apiVersion >= Build.VERSION_CODES.O) block() else fallback() return if (this.apiVersion >= Build.VERSION_CODES.O) block() else fallback()
} }
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
fun DeviceMeta.isAtLeastS() = this.apiVersion >= Build.VERSION_CODES.S fun DeviceMeta.isAtLeastS() = this.apiVersion >= Build.VERSION_CODES.S
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O, lambda = 0)
fun DeviceMeta.onAtLeastO(block: () -> Unit) { fun DeviceMeta.onAtLeastO(block: () -> Unit) {
if (this.apiVersion >= Build.VERSION_CODES.O) block() whenXOrHigher(Build.VERSION_CODES.O, block, fallback = {})
}
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R, lambda = 0)
fun DeviceMeta.onAtLeastR(block: () -> Unit) {
whenXOrHigher(Build.VERSION_CODES.R, block, fallback = {})
} }
inline fun <T> DeviceMeta.whenPOrHigher(block: () -> T, fallback: () -> T) = whenXOrHigher(Build.VERSION_CODES.P, block, fallback) inline fun <T> DeviceMeta.whenPOrHigher(block: () -> T, fallback: () -> T) = whenXOrHigher(Build.VERSION_CODES.P, block, fallback)
inline fun <T> DeviceMeta.whenOOrHigher(block: () -> T, fallback: () -> T) = whenXOrHigher(Build.VERSION_CODES.O, block, fallback) inline fun <T> DeviceMeta.whenOOrHigher(block: () -> T, fallback: () -> T) = whenXOrHigher(Build.VERSION_CODES.O, block, fallback)
@ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
inline fun <T> DeviceMeta.whenXOrHigher(version: Int, block: () -> T, fallback: () -> T): T { inline fun <T> DeviceMeta.whenXOrHigher(version: Int, block: () -> T, fallback: () -> T): T {
return if (this.apiVersion >= version) block() else fallback() return if (this.apiVersion >= version) block() else fallback()
} }

View File

@ -8,6 +8,7 @@ android {
dependencies { dependencies {
implementation project(":domains:android:compose-core") implementation project(":domains:android:compose-core")
implementation project(":domains:android:imageloader")
implementation "chat-engine:chat-engine" implementation "chat-engine:chat-engine"
implementation 'screen-state:screen-android' implementation 'screen-state:screen-android'
implementation project(":features:messenger") implementation project(":features:messenger")

View File

@ -4,19 +4,17 @@ import android.content.Context
import app.dapk.st.core.JobBag import app.dapk.st.core.JobBag
import app.dapk.st.core.ProvidableModule import app.dapk.st.core.ProvidableModule
import app.dapk.st.directory.state.DirectoryEvent import app.dapk.st.directory.state.DirectoryEvent
import app.dapk.st.directory.state.DirectoryState
import app.dapk.st.directory.state.directoryReducer import app.dapk.st.directory.state.directoryReducer
import app.dapk.st.engine.ChatEngine import app.dapk.st.engine.ChatEngine
import app.dapk.st.state.createStateViewModel import app.dapk.st.imageloader.IconLoader
class DirectoryModule( class DirectoryModule(
private val context: Context, private val context: Context,
private val chatEngine: ChatEngine, private val chatEngine: ChatEngine,
private val iconLoader: IconLoader,
) : ProvidableModule { ) : ProvidableModule {
fun directoryState(): DirectoryState { fun directoryReducer(eventEmitter: suspend (DirectoryEvent) -> Unit) = directoryReducer(chatEngine, shortcutHandler(), JobBag(), eventEmitter)
return createStateViewModel { directoryReducer(it) }
}
fun directoryReducer(eventEmitter: suspend (DirectoryEvent) -> Unit) = directoryReducer(chatEngine, ShortcutHandler(context), JobBag(), eventEmitter) private fun shortcutHandler() = ShortcutHandler(context, iconLoader)
} }

View File

@ -1,19 +1,24 @@
package app.dapk.st.directory package app.dapk.st.directory
import android.content.Context import android.content.Context
import android.content.pm.ShortcutInfo
import androidx.core.app.Person import androidx.core.app.Person
import androidx.core.content.LocusIdCompat
import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import app.dapk.st.engine.RoomOverview import app.dapk.st.engine.RoomOverview
import app.dapk.st.imageloader.IconLoader
import app.dapk.st.matrix.common.RoomId import app.dapk.st.matrix.common.RoomId
import app.dapk.st.messenger.MessengerActivity import app.dapk.st.messenger.MessengerActivity
internal class ShortcutHandler(private val context: Context) { internal class ShortcutHandler(
private val context: Context,
private val iconLoader: IconLoader,
) {
private val cachedRoomIds = mutableListOf<RoomId>() private val cachedRoomIds = mutableListOf<RoomId>()
fun onDirectoryUpdate(overviews: List<RoomOverview>) { suspend fun onDirectoryUpdate(overviews: List<RoomOverview>) {
val update = overviews.map { it.roomId } val update = overviews.map { it.roomId }
if (cachedRoomIds != update) { if (cachedRoomIds != update) {
cachedRoomIds.clear() cachedRoomIds.clear()
@ -21,12 +26,14 @@ internal class ShortcutHandler(private val context: Context) {
val maxShortcutCountPerActivity = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context) val maxShortcutCountPerActivity = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context)
overviews overviews
.sortedByDescending { it.lastMessage?.utcTimestamp }
.take(maxShortcutCountPerActivity) .take(maxShortcutCountPerActivity)
.forEachIndexed { index, room -> .forEachIndexed { index, room ->
val build = ShortcutInfoCompat.Builder(context, room.roomId.value) val build = ShortcutInfoCompat.Builder(context, room.roomId.value)
.setShortLabel(room.roomName ?: "N/A") .setShortLabel(room.roomName ?: "N/A")
.setLongLabel(room.roomName ?: "N/A") .setLongLabel(room.roomName ?: "N/A")
.setRank(index) .setRank(index)
.setLocusId((LocusIdCompat(room.roomId.value)))
.run { .run {
this.setPerson( this.setPerson(
Person.Builder() Person.Builder()
@ -35,9 +42,14 @@ internal class ShortcutHandler(private val context: Context) {
.build() .build()
) )
} }
.run {
room.roomAvatarUrl?.let { iconLoader.load(it.value) }?.let {
this.setIcon(IconCompat.createFromIcon(context, it))
} ?: this
}
.setIntent(MessengerActivity.newShortcutInstance(context, room.roomId)) .setIntent(MessengerActivity.newShortcutInstance(context, room.roomId))
.setLongLived(true) .setLongLived(true)
.setCategories(setOf(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION)) .setIsConversation()
.build() .build()
ShortcutManagerCompat.pushDynamicShortcut(context, build) ShortcutManagerCompat.pushDynamicShortcut(context, build)
} }

View File

@ -3,6 +3,7 @@ package app.dapk.st.messenger
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.LocusId
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -10,11 +11,8 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import app.dapk.st.core.AndroidUri import app.dapk.st.core.*
import app.dapk.st.core.DapkActivity
import app.dapk.st.core.MimeType
import app.dapk.st.core.extensions.unsafeLazy import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.core.module
import app.dapk.st.matrix.common.RoomId import app.dapk.st.matrix.common.RoomId
import app.dapk.st.messenger.gallery.GetImageFromGallery import app.dapk.st.messenger.gallery.GetImageFromGallery
import app.dapk.st.messenger.state.ComposerStateChange import app.dapk.st.messenger.state.ComposerStateChange
@ -58,6 +56,8 @@ class MessengerActivity : DapkActivity() {
val payload = readPayload<MessagerActivityPayload>() val payload = readPayload<MessagerActivityPayload>()
val factory = ImageRequest.Builder(applicationContext).fetcherFactory(module.decryptingFetcherFactory(RoomId(payload.roomId))) val factory = ImageRequest.Builder(applicationContext).fetcherFactory(module.decryptingFetcherFactory(RoomId(payload.roomId)))
module.deviceMeta.onAtLeastR { setLocusContext(LocusId(payload.roomId), savedInstanceState) }
val galleryLauncher = registerForActivityResult(GetImageFromGallery()) { val galleryLauncher = registerForActivityResult(GetImageFromGallery()) {
it?.let { uri -> it?.let { uri ->
state.dispatch( state.dispatch(

View File

@ -16,7 +16,7 @@ class MessengerModule(
private val chatEngine: ChatEngine, private val chatEngine: ChatEngine,
private val context: Context, private val context: Context,
private val messageOptionsStore: MessageOptionsStore, private val messageOptionsStore: MessageOptionsStore,
private val deviceMeta: DeviceMeta, val deviceMeta: DeviceMeta,
) : ProvidableModule { ) : ProvidableModule {
internal fun messengerState(launchPayload: MessagerActivityPayload): MessengerState { internal fun messengerState(launchPayload: MessagerActivityPayload): MessengerState {

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.Notification import android.app.Notification
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.LocusId
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import app.dapk.st.core.DeviceMeta import app.dapk.st.core.DeviceMeta
import app.dapk.st.core.isAtLeastO import app.dapk.st.core.isAtLeastO
@ -41,7 +42,8 @@ class AndroidNotificationBuilder(
} }
.ifNotNull(notification.category) { setCategory(it) } .ifNotNull(notification.category) { setCategory(it) }
.ifNotNull(notification.shortcutId) { .ifNotNull(notification.shortcutId) {
deviceMeta.onAtLeastO { setShortcutId(notification.shortcutId) } setLocusId(LocusId(it))
deviceMeta.onAtLeastO { setShortcutId(it) }
} }
.ifNotNull(notification.smallIcon) { setSmallIcon(it) } .ifNotNull(notification.smallIcon) { setSmallIcon(it) }
.ifNotNull(notification.largeIcon) { setLargeIcon(it) } .ifNotNull(notification.largeIcon) { setLargeIcon(it) }

View File

@ -13,6 +13,7 @@ sqldelight = { id = "com.squareup.sqldelight", version.ref = "sqldelight" }
[libraries] [libraries]
android-desugar = { group = "com.android.tools", name = "desugar_jdk_libs", version = "1.1.5" } android-desugar = { group = "com.android.tools", name = "desugar_jdk_libs", version = "1.1.5" }
androidx-annotation = { group = "androidx.annotation", name = "annotation", version = "1.5.0" }
compose-coil = { group = "io.coil-kt", name = "coil-compose", version = "2.2.2" } compose-coil = { group = "io.coil-kt", name = "coil-compose", version = "2.2.2" }
accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version = "0.28.0" } accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version = "0.28.0" }