From 9983871db7cea4035aa18e9113e57426aa756c34 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Oct 2025 09:32:52 +0200 Subject: [PATCH 01/13] Introduce WorkerDataConverter to avoid hard coded Json key and ensure serializing/deserializing is performed at the same place. --- .../NotificationResolverQueue.kt | 6 +- .../workmanager/FetchNotificationsWorker.kt | 16 +---- .../SyncNotificationWorkManagerRequest.kt | 38 ++-------- .../impl/workmanager/WorkerDataConverter.kt | 72 +++++++++++++++++++ .../push/impl/push/DefaultPushHandlerTest.kt | 3 +- .../FetchNotificationWorkerTest.kt | 2 +- .../SyncNotificationWorkManagerRequestTest.kt | 2 +- .../workmanager/WorkerDataConverterTest.kt | 53 ++++++++++++++ 8 files changed, 140 insertions(+), 52 deletions(-) create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverter.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverterTest.kt diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResolverQueue.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResolverQueue.kt index 928f7bfee1..58ff8a5064 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResolverQueue.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResolverQueue.kt @@ -11,13 +11,13 @@ import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn -import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.push.api.push.NotificationEventRequest import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent import io.element.android.libraries.push.impl.workmanager.SyncNotificationWorkManagerRequest +import io.element.android.libraries.push.impl.workmanager.WorkerDataConverter import io.element.android.libraries.workmanager.api.WorkManagerScheduler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,7 +50,7 @@ class DefaultNotificationResolverQueue( private val appCoroutineScope: CoroutineScope, private val workManagerScheduler: WorkManagerScheduler, private val featureFlagService: FeatureFlagService, - private val json: JsonProvider, + private val workerDataConverter: WorkerDataConverter, ) : NotificationResolverQueue { companion object { private const val BATCH_WINDOW_MS = 250L @@ -101,7 +101,7 @@ class DefaultNotificationResolverQueue( SyncNotificationWorkManagerRequest( sessionId = sessionId, notificationEventRequests = requests, - json = json, + workerDataConverter = workerDataConverter, ) ) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationsWorker.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationsWorker.kt index 4839bc193f..fadd1a377c 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationsWorker.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationsWorker.kt @@ -18,7 +18,6 @@ import dev.zacsweers.metro.ContributesIntoMap import dev.zacsweers.metro.binding import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus -import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.annotations.ApplicationContext @@ -47,20 +46,11 @@ class FetchNotificationsWorker( private val workManagerScheduler: WorkManagerScheduler, private val syncOnNotifiableEvent: SyncOnNotifiableEvent, private val coroutineDispatchers: CoroutineDispatchers, - private val json: JsonProvider, + private val workerDataConverter: WorkerDataConverter, ) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result = withContext(coroutineDispatchers.io) { Timber.d("FetchNotificationsWorker started") - val rawRequestsJson = inputData.getString("requests") ?: return@withContext Result.failure() - val requests = runCatchingExceptions { - json().decodeFromString>(rawRequestsJson).map { it.toRequest() } - }.getOrElse { - Timber.e(it, "Failed to deserialize notification requests") - return@withContext Result.failure() - } - - Timber.d("Deserialized ${requests.size} requests") - + val requests = workerDataConverter.deserialize(inputData) ?: return@withContext Result.failure() // Wait for network to be available, but not more than 10 seconds val hasNetwork = withTimeoutOrNull(10.seconds) { networkMonitor.connectivity.first { it == NetworkStatus.Connected } @@ -97,7 +87,7 @@ class FetchNotificationsWorker( SyncNotificationWorkManagerRequest( sessionId = failedSessionId, notificationEventRequests = requestsToRetry, - json = json, + workerDataConverter = workerDataConverter, ) ) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequest.kt index db08b0041d..b0aabe1cc7 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequest.kt @@ -10,11 +10,6 @@ package io.element.android.libraries.push.impl.workmanager import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OutOfQuotaPolicy import androidx.work.WorkRequest -import androidx.work.workDataOf -import io.element.android.libraries.androidutils.json.JsonProvider -import io.element.android.libraries.core.extensions.runCatchingExceptions -import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.push.NotificationEventRequest import io.element.android.libraries.workmanager.api.WorkManagerRequest @@ -28,24 +23,19 @@ import java.security.InvalidParameterException class SyncNotificationWorkManagerRequest( private val sessionId: SessionId, private val notificationEventRequests: List, - private val json: JsonProvider, + private val workerDataConverter: WorkerDataConverter, ) : WorkManagerRequest { override fun build(): Result { if (notificationEventRequests.isEmpty()) { return Result.failure(InvalidParameterException("notificationEventRequests cannot be empty")) } - - val json = runCatchingExceptions { json().encodeToString(notificationEventRequests.map { it.toData() }) } - .getOrElse { - Timber.e(it, "Failed to serialize notification requests") - return Result.failure(it) - } - + val data = workerDataConverter.serialize(notificationEventRequests).getOrElse { + return Result.failure(it) + } Timber.d("Scheduling ${notificationEventRequests.size} notification requests with WorkManager for $sessionId") - return Result.success( OneTimeWorkRequestBuilder() - .setInputData(workDataOf("requests" to json)) + .setInputData(data) .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .setTraceTag(workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC)) // TODO investigate using this instead of the resolver queue @@ -64,23 +54,5 @@ class SyncNotificationWorkManagerRequest( val eventId: String, @SerialName("provider_info") val providerInfo: String, - ) { - fun toRequest(): NotificationEventRequest { - return NotificationEventRequest( - sessionId = SessionId(sessionId), - roomId = RoomId(roomId), - eventId = EventId(eventId), - providerInfo = providerInfo, - ) - } - } -} - -private fun NotificationEventRequest.toData(): SyncNotificationWorkManagerRequest.Data { - return SyncNotificationWorkManagerRequest.Data( - sessionId = sessionId.value, - roomId = roomId.value, - eventId = eventId.value, - providerInfo = providerInfo, ) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverter.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverter.kt new file mode 100644 index 0000000000..ce961e1dc2 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverter.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +import androidx.work.Data +import androidx.work.workDataOf +import dev.zacsweers.metro.Inject +import io.element.android.libraries.androidutils.json.JsonProvider +import io.element.android.libraries.core.extensions.runCatchingExceptions +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.push.api.push.NotificationEventRequest +import timber.log.Timber + +@Inject +class WorkerDataConverter( + private val json: JsonProvider, +) { + fun serialize(notificationEventRequests: List): Result { + return runCatchingExceptions { json().encodeToString(notificationEventRequests.map { it.toData() }) } + .onFailure { + Timber.e(it, "Failed to serialize notification requests") + } + .map { str -> + workDataOf(REQUESTS_KEY to str) + } + } + + fun deserialize(data: Data): List? { + val rawRequestsJson = data.getString(REQUESTS_KEY) ?: return null + return runCatchingExceptions { + json().decodeFromString>(rawRequestsJson).map { it.toRequest() } + }.fold( + onSuccess = { + Timber.d("Deserialized ${it.size} requests") + it + }, + onFailure = { + Timber.e(it, "Failed to deserialize notification requests") + null + } + ) + } + + companion object { + private const val REQUESTS_KEY = "requests" + } +} + +private fun NotificationEventRequest.toData(): SyncNotificationWorkManagerRequest.Data { + return SyncNotificationWorkManagerRequest.Data( + sessionId = sessionId.value, + roomId = roomId.value, + eventId = eventId.value, + providerInfo = providerInfo, + ) +} + +private fun SyncNotificationWorkManagerRequest.Data.toRequest(): NotificationEventRequest { + return NotificationEventRequest( + sessionId = SessionId(sessionId), + roomId = RoomId(roomId), + eventId = EventId(eventId), + providerInfo = providerInfo, + ) +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt index 4bfb944202..870b9e1e1a 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt @@ -46,6 +46,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent import io.element.android.libraries.push.impl.test.DefaultTestPush import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler +import io.element.android.libraries.push.impl.workmanager.WorkerDataConverter import io.element.android.libraries.pushproviders.api.PushData import io.element.android.libraries.pushstore.api.UserPushStore import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret @@ -715,7 +716,7 @@ class DefaultPushHandlerTest { appCoroutineScope = backgroundScope, workManagerScheduler = workManagerScheduler, featureFlagService = featureFlagService, - json = DefaultJsonProvider(), + workerDataConverter = WorkerDataConverter(DefaultJsonProvider()), ), appCoroutineScope = backgroundScope, fallbackNotificationFactory = FallbackNotificationFactory( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationWorkerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationWorkerTest.kt index 4a98ed970a..74241fecd8 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationWorkerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationWorkerTest.kt @@ -175,7 +175,7 @@ class FetchNotificationWorkerTest { workManagerScheduler = workManagerScheduler, syncOnNotifiableEvent = syncOnNotifiableEvent, coroutineDispatchers = testCoroutineDispatchers(), - json = DefaultJsonProvider(), + workerDataConverter = WorkerDataConverter(DefaultJsonProvider()), ) private fun TestScope.createWorkerParams( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt index 91937a2a67..fcefd8ab8b 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt @@ -58,5 +58,5 @@ private fun createSyncNotificationWorkManagerRequest( ) = SyncNotificationWorkManagerRequest( sessionId = sessionId, notificationEventRequests = notificationEventRequests, - json = DefaultJsonProvider(), + workerDataConverter = WorkerDataConverter(DefaultJsonProvider()), ) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverterTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverterTest.kt new file mode 100644 index 0000000000..a4a53208b3 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverterTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.json.DefaultJsonProvider +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID_2 +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID_2 +import io.element.android.libraries.push.api.push.NotificationEventRequest +import org.junit.Test + +class WorkerDataConverterTest { + @Test + fun `ensure identity when serializing - deserializing an empty list`() { + testIdentity(emptyList()) + } + + @Test + fun `ensure identity when serializing - deserializing a list`() { + testIdentity( + listOf( + NotificationEventRequest( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, + providerInfo = "info1", + ), + NotificationEventRequest( + sessionId = A_SESSION_ID_2, + roomId = A_ROOM_ID_2, + eventId = AN_EVENT_ID_2, + providerInfo = "info2", + ), + ) + ) + } + + private fun testIdentity(data: List) { + val sut = WorkerDataConverter(DefaultJsonProvider()) + val serialized = sut.serialize(data).getOrThrow() + val result = sut.deserialize(serialized) + assertThat(result).isEqualTo(data) + } +} From 937519639df02b903a6d23d66a54ebd235ff6ba0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Oct 2025 09:59:24 +0200 Subject: [PATCH 02/13] Add missing test. --- .../libraries/androidutils/json/JsonProvider.kt | 2 +- .../SyncNotificationWorkManagerRequestTest.kt | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/json/JsonProvider.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/json/JsonProvider.kt index 08226060db..2e91cd6784 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/json/JsonProvider.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/json/JsonProvider.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.json.Json /** * Provides a Json instance configured to ignore unknown keys. */ -interface JsonProvider : Provider +fun interface JsonProvider : Provider @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt index fcefd8ab8b..ebbe4eb865 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt @@ -49,14 +49,24 @@ class SyncNotificationWorkManagerRequestTest { assertThat(result.isFailure).isTrue() } - // TODO add test for invalid serialization (how?) + @Test + fun `build - invalid serialization`() = runTest { + val request = createSyncNotificationWorkManagerRequest( + sessionId = A_SESSION_ID, + notificationEventRequests = listOf(aNotificationEventRequest()), + workerDataConverter = WorkerDataConverter({ error("error during serialization") }) + ) + val result = request.build() + assertThat(result.isFailure).isTrue() + } } private fun createSyncNotificationWorkManagerRequest( sessionId: SessionId, notificationEventRequests: List, + workerDataConverter: WorkerDataConverter = WorkerDataConverter(DefaultJsonProvider()) ) = SyncNotificationWorkManagerRequest( sessionId = sessionId, notificationEventRequests = notificationEventRequests, - workerDataConverter = WorkerDataConverter(DefaultJsonProvider()), + workerDataConverter = workerDataConverter, ) From d0c5abadbdae9bd3e1bcab05c6dbda7f81496db2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 13:22:00 +0200 Subject: [PATCH 03/13] Mark as read: use icon from compound --- .../factories/action/MarkAsReadActionFactory.kt | 3 ++- .../ic_material_done_all_white.png | Bin 398 -> 0 bytes 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100755 libraries/push/impl/src/main/res/drawable-xxhdpi/ic_material_done_all_white.png diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt index 1feaee7954..22e08ff550 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt @@ -14,6 +14,7 @@ import androidx.core.app.NotificationCompat import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationActionIds @@ -46,7 +47,7 @@ class MarkAsReadActionFactory( ) return NotificationCompat.Action.Builder( - R.drawable.ic_material_done_all_white, + CompoundDrawables.ic_compound_mark_as_read, stringProvider.getString(R.string.notification_room_action_mark_as_read), pendingIntent ) diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/ic_material_done_all_white.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/ic_material_done_all_white.png deleted file mode 100755 index 1f3132a3f2f1ef1d4f4683e148fd351eec398569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 398 zcmV;90df9`P)hdV05a26EH^Do>3VzkAd1j7p1$9mlKEvS8<$lOjC~rtLv7=+%qJ9CEuV( zSEHe6nx<)*rU}Pk{Y6Ypiv{D#MVl4ZDLH_jM4J@=Hz_$@(l-a-HYLYN+TsB8Q*vOm zgChx2PEAhdcR54lDCd802Fm#nXRVyS!8x)xSu_qEUO1~9dvU}=qQ|CKOfAw=Y|bSgrDIE*M32wXV#(VVG#2;9V!lm^$_XYl#W;baVj;qDlIHu6 z#5f+3ad~;LNU~q_0Fv|rqZUc#y Date: Fri, 24 Oct 2025 13:23:59 +0200 Subject: [PATCH 04/13] Accept / reject invitation actions: use icons from compound --- .../action/AcceptInvitationActionFactory.kt | 4 ++-- .../action/RejectInvitationActionFactory.kt | 4 ++-- .../vector_notification_accept_invitation.png | Bin 473 -> 0 bytes .../vector_notification_reject_invitation.png | Bin 309 -> 0 bytes 4 files changed, 4 insertions(+), 4 deletions(-) delete mode 100755 libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_accept_invitation.png delete mode 100755 libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_reject_invitation.png diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/AcceptInvitationActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/AcceptInvitationActionFactory.kt index 6e3fe95742..801ecbbc90 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/AcceptInvitationActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/AcceptInvitationActionFactory.kt @@ -14,8 +14,8 @@ import androidx.core.app.NotificationCompat import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.di.annotations.ApplicationContext -import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -46,7 +46,7 @@ class AcceptInvitationActionFactory( PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Action.Builder( - R.drawable.vector_notification_accept_invitation, + CompoundDrawables.ic_compound_check, stringProvider.getString(CommonStrings.action_accept), pendingIntent ).build() diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/RejectInvitationActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/RejectInvitationActionFactory.kt index 73b3fc2fd9..fe33495387 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/RejectInvitationActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/RejectInvitationActionFactory.kt @@ -14,8 +14,8 @@ import androidx.core.app.NotificationCompat import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.di.annotations.ApplicationContext -import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -46,7 +46,7 @@ class RejectInvitationActionFactory( PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Action.Builder( - R.drawable.vector_notification_reject_invitation, + CompoundDrawables.ic_compound_close, stringProvider.getString(CommonStrings.action_reject), pendingIntent ).build() diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_accept_invitation.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_accept_invitation.png deleted file mode 100755 index eb2be2518782887fc5a85e5e7458088b76b63198..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473 zcmV;~0Ve*5P)n0!$1^!3wXZ3TqH33 zIbSAo5)cGI5ClOG+%o`F`=!Wvn3w@e&Ur+kL#WP#gl`PNp?PO?K-R}^Z7l9ts#c{;+-<8Y?QYL% ziW`;0IgxdWI7hOMiCgF59Lwqwx8|A~*1j5yrZ~ehFSRpH+png${phh9tc&y94YrB% z+`6kql~Uhd5>;_&2*2g3;=EU<8Yzf41q&7gL{u7si4;^EK@bE%5IXn)RHBsDDO{M4 P00000NkvXXu0mjf+Ct1x diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_reject_invitation.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_reject_invitation.png deleted file mode 100755 index 51b4401ca053ca8cad6e9903646709a2f44444df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw{&>1LhEy=VysgJ$~x+g}6OvsxLimbaeN-!xQItoH=3nd`|H@dF!kYp_dChV`b9AudQLRn7yg2 zv~UiON9*MGOYAy6D=_*g@;c3(5`S(Hi^X;wk8Ms*3SMP^!WTf z-E_DAd$kLMWY`Z`);MuKF9=d$?_mzLoFj7Xp~|_3ODg!(3;AT&wLoS^O+0tsvcZJ& zD(m_$dNm(!9@n+CTyDa$w$*a^-$!>qK3P{4end8+qbAwoFEAV!JYD@<);T3K0RXh> Be)#|Z From 8ee422508b9dc7712e7f8a6c95543d8fbabddaa5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 13:25:38 +0200 Subject: [PATCH 05/13] Quick reply action: use icon from compound --- .../factories/action/QuickReplyActionFactory.kt | 3 ++- .../vector_notification_quick_reply.png | Bin 269 -> 0 bytes 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100755 libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_quick_reply.png diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt index eb54bf9b36..a716839ea9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt @@ -16,6 +16,7 @@ import androidx.core.app.RemoteInput import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -45,7 +46,7 @@ class QuickReplyActionFactory( .build() return NotificationCompat.Action.Builder( - R.drawable.vector_notification_quick_reply, + CompoundDrawables.ic_compound_reply, stringProvider.getString(R.string.notification_room_action_quick_reply), replyPendingIntent ) diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_quick_reply.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_quick_reply.png deleted file mode 100755 index 4af4ae634b4c805b2b13c209262bba92d4ef5bdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawu6VjQhEy=Vy==(G$SBbIuwHt% zp3_UKM>>291zL-BrWtE$WMrxY&q^|md-S)saec+%`%mT+^YR*%zN!3_;NI;JthdL` z{KCnCCY1w%imdJpA~Ld3FOd=(E`?RG0HH+`PU-POx#QYR|iePyTZek-J~XvsWJ zNp;6pnWrr{gH+WWS3OqoV4b69!Fg%ZjL8ar=LoBC`b|jln)7GMS;OOVHd`te@;#f8 z>^I4HhOyA|j6Ta%GFL2C6b3wAaO`BGUn!j9*xM|{uGv?7Zl}4O+1d4_3mOH1{$ucT L^>bP0l+XkKk9}#` From 760ca8e8106f11a5f3052fba7fbfc49519cfd6bf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 15:09:45 +0200 Subject: [PATCH 06/13] Fix warning --- .../kotlin/io/element/android/appconfig/NotificationConfig.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt index 41acf5d266..1e0f5c2f55 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt @@ -7,8 +7,8 @@ package io.element.android.appconfig -import android.graphics.Color import androidx.annotation.ColorInt +import androidx.core.graphics.toColorInt object NotificationConfig { /** @@ -27,5 +27,5 @@ object NotificationConfig { const val SHOW_QUICK_REPLY_ACTION = true @ColorInt - val NOTIFICATION_ACCENT_COLOR: Int = Color.parseColor("#FF0DBD8B") + val NOTIFICATION_ACCENT_COLOR: Int = "#FF0DBD8B".toColorInt() } From e689eaf73a077327c0f110655add8d3fdb13b9a5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 23 Oct 2025 16:01:47 +0200 Subject: [PATCH 07/13] design(space): let divider be full width # Conflicts: # features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt --- .../home/impl/spaces/HomeSpacesView.kt | 33 ++++--- .../features/space/impl/root/SpaceView.kt | 61 +++++++------ .../matrix/ui/components/SpaceRoomItemView.kt | 88 +++++++++---------- 3 files changed, 92 insertions(+), 90 deletions(-) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt index bc1f474956..12ef091b3a 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt @@ -10,6 +10,7 @@ package io.element.android.features.home.impl.spaces import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter @@ -57,20 +58,24 @@ fun HomeSpacesView( item { HorizontalDivider() } - state.spaceRooms.forEach { spaceRoom -> - item(spaceRoom.roomId) { - val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED - SpaceRoomItemView( - spaceRoom = spaceRoom, - showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, - hideAvatars = isInvitation && state.hideInvitesAvatar, - onClick = { - onSpaceClick(spaceRoom.roomId) - }, - onLongClick = { - // TODO - }, - ) + itemsIndexed( + items = state.spaceRooms, + key = { _, spaceRoom -> spaceRoom.roomId } + ) { index, spaceRoom -> + val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED + SpaceRoomItemView( + spaceRoom = spaceRoom, + showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, + hideAvatars = isInvitation && state.hideInvitesAvatar, + onClick = { + onSpaceClick(spaceRoom.roomId) + }, + onLongClick = { + // TODO + }, + ) + if (index != state.spaceRooms.lastIndex) { + HorizontalDivider() } } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt index cd722ac6f3..cbcae289e0 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -182,32 +183,36 @@ private fun SpaceViewContent( HorizontalDivider() } } - state.children.forEach { spaceRoom -> - item { - val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED - val isCurrentlyJoining = state.isJoining(spaceRoom.roomId) - SpaceRoomItemView( - spaceRoom = spaceRoom, - showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, - hideAvatars = isInvitation && state.hideInvitesAvatar, - onClick = { - onRoomClick(spaceRoom) + itemsIndexed( + items = state.children, + key = { _, spaceRoom -> spaceRoom.roomId } + ) { index, spaceRoom -> + val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED + val isCurrentlyJoining = state.isJoining(spaceRoom.roomId) + SpaceRoomItemView( + spaceRoom = spaceRoom, + showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, + hideAvatars = isInvitation && state.hideInvitesAvatar, + onClick = { + onRoomClick(spaceRoom) + }, + onLongClick = { + // TODO + }, + trailingAction = spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { + state.eventSink(SpaceEvents.Join(spaceRoom)) + }, + bottomAction = spaceRoom.inviteButtons( + onAcceptClick = { + state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) }, - onLongClick = { - // TODO - }, - trailingAction = spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { - state.eventSink(SpaceEvents.Join(spaceRoom)) - }, - bottomAction = spaceRoom.inviteButtons( - onAcceptClick = { - state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) - }, - onDeclineClick = { - state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) - } - ) + onDeclineClick = { + state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) + } ) + ) + if (index != state.children.lastIndex) { + HorizontalDivider() } } if (state.hasMoreToLoad) { @@ -328,10 +333,10 @@ private fun SpaceAvatarAndNameRow( ) Text( modifier = Modifier - .padding(horizontal = 8.dp) - .semantics { - heading() - }, + .padding(horizontal = 8.dp) + .semantics { + heading() + }, text = name ?: stringResource(CommonStrings.common_no_space_name), style = ElementTheme.typography.fontBodyLgMedium, fontStyle = FontStyle.Italic.takeIf { name == null }, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomItemView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomItemView.kt index 90ed843483..1bede0784b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomItemView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomItemView.kt @@ -43,7 +43,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.unreadIndicator @@ -81,56 +80,50 @@ fun SpaceRoomItemView( interactionSource = remember { MutableInteractionSource() } ) .onKeyboardContextMenuAction { onLongClick } - Box(modifier = modifier.then(clickModifier)) { - Column( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), + Column( + modifier = modifier + .then(clickModifier) + .padding(horizontal = 16.dp, vertical = 12.dp), + ) { + SpaceRoomItemScaffold( + avatarData = spaceRoom.getAvatarData(AvatarSize.SpaceListItem), + isSpace = spaceRoom.isSpace, + hideAvatars = hideAvatars, + heroes = spaceRoom.heroes + .map { hero -> hero.getAvatarData(AvatarSize.SpaceListItem) } + .toImmutableList(), + trailingAction = trailingAction, ) { - SpaceRoomItemScaffold( - avatarData = spaceRoom.getAvatarData(AvatarSize.SpaceListItem), - isSpace = spaceRoom.isSpace, - hideAvatars = hideAvatars, - heroes = spaceRoom.heroes - .map { hero -> hero.getAvatarData(AvatarSize.SpaceListItem) } - .toImmutableList(), - trailingAction = trailingAction, - ) { - NameAndIndicatorRow( - name = spaceRoom.displayName, - showIndicator = showUnreadIndicator + NameAndIndicatorRow( + name = spaceRoom.displayName, + showIndicator = showUnreadIndicator + ) + Spacer(modifier = Modifier.height(1.dp)) + SubtitleRow( + visibilityIcon = spaceRoom.visibilityIcon(), + subtitle = spaceRoom.subtitle() + ) + Spacer(modifier = Modifier.height(1.dp)) + val info = spaceRoom.info() + if (info.isNotBlank()) { + Text( + modifier = Modifier.weight(1f), + style = ElementTheme.typography.fontBodyMdRegular, + text = info, + color = ElementTheme.colors.textSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) - Spacer(modifier = Modifier.height(1.dp)) - SubtitleRow( - visibilityIcon = spaceRoom.visibilityIcon(), - subtitle = spaceRoom.subtitle() - ) - Spacer(modifier = Modifier.height(1.dp)) - val info = spaceRoom.info() - if (info.isNotBlank()) { - Text( - modifier = Modifier.weight(1f), - style = ElementTheme.typography.fontBodyMdRegular, - text = info, - color = ElementTheme.colors.textSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - if (bottomAction != null) { - Spacer(modifier = Modifier.height(12.dp)) - // Match the padding of the text content (avatar + spacer) - Box(modifier = Modifier.padding(start = AvatarSize.SpaceListItem.dp + 16.dp)) { - bottomAction() - } - Spacer(modifier = Modifier.height(4.dp)) } } - HorizontalDivider( - modifier = Modifier - // Match the padding of the text content (padding + avatar + spacer) - .padding(start = AvatarSize.SpaceListItem.dp + 16.dp + 16.dp) - .align(Alignment.BottomCenter) - ) + if (bottomAction != null) { + Spacer(modifier = Modifier.height(12.dp)) + // Match the padding of the text content (avatar + spacer) + Box(modifier = Modifier.padding(start = AvatarSize.SpaceListItem.dp + 16.dp)) { + bottomAction() + } + Spacer(modifier = Modifier.height(4.dp)) + } } } @@ -264,7 +257,6 @@ internal fun SpaceRoomItemViewPreview(@PreviewParameter(SpaceRoomProvider::class hideAvatars = false, onClick = {}, onLongClick = {}, - modifier = Modifier.fillMaxWidth().padding(8.dp), bottomAction = if (spaceRoom.state == CurrentUserMembership.INVITED) { { InviteButtonsRowMolecule({}, {}) } } else { From d75663f09ba25590866ae6065202257f2135374c Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 24 Oct 2025 14:22:53 +0000 Subject: [PATCH 08/13] Update screenshots --- .../features.home.impl.spaces_HomeSpacesView_Day_0_en.png | 4 ++-- .../features.home.impl.spaces_HomeSpacesView_Day_1_en.png | 4 ++-- .../features.home.impl.spaces_HomeSpacesView_Night_0_en.png | 4 ++-- .../features.home.impl.spaces_HomeSpacesView_Night_1_en.png | 4 ++-- .../snapshots/images/features.home.impl_HomeView_Day_4_en.png | 4 ++-- .../images/features.home.impl_HomeView_Night_4_en.png | 4 ++-- .../images/features.space.impl.root_SpaceView_Day_3_en.png | 4 ++-- .../images/features.space.impl.root_SpaceView_Day_4_en.png | 4 ++-- .../images/features.space.impl.root_SpaceView_Night_3_en.png | 4 ++-- .../images/features.space.impl.root_SpaceView_Night_4_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_0_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_1_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_2_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_5_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_6_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png | 4 ++-- ...raries.matrix.ui.components_SpaceRoomItemView_Day_8_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_0_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_1_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_2_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_5_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_6_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png | 4 ++-- ...ries.matrix.ui.components_SpaceRoomItemView_Night_8_en.png | 4 ++-- 28 files changed, 56 insertions(+), 56 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png index 8bf9eeded7..eebbc79b07 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40570506b75d7cb582cee70eb3d6453b673087a17a18d8ef148e3ff5edb6f1b9 -size 89228 +oid sha256:388207cd5b424fbb95f070eac393db9330ae1b641795d1bb815874435ef9f623 +size 89027 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_1_en.png index b61f724c29..834f73ab9f 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:491d0d8f7d7e269b13548c48a6eacb8fa4df5e2798aae5b3938e7eb7368ce3f9 -size 41251 +oid sha256:542d8ba6a6031fe2789cf111f333eef22acf95281f57421ace2c7b5b0a599cc2 +size 41140 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png index 20ce14c682..187ee16663 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fb98e398abcb465b94d4138eecbccf076e229cb0807f6543d4f77ebb0353499 -size 87390 +oid sha256:134e561fc4082339725c241a79fa55e0b5b1e134c046d4454cb7a9e71ea5e1b7 +size 87174 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_1_en.png index 0192faf169..7dc28605d2 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bee6454b9fab1a579e86bece32c0d6134d08fed5a63fecf247a58d7acba142d -size 40125 +oid sha256:c31cd78bc054610be05012cdba7eb0cbc770435b0e12bc065f6eae4a773ca39e +size 40121 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png index 5c7dae4a0e..5cd1c50997 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef2b3724cdd28175824554f800019fa663be341f0854751b50321e9b73203b6a -size 54275 +oid sha256:27c5418d421ca6cf0069e34ca3e22ca807203d252b9c1424eca447f070fbbbdf +size 54177 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png index 00edb13227..d6b722a3f3 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b6fc1c78e05737b3231172b2b72b532b769ae9928ddd6674a864bee56566f6e -size 52623 +oid sha256:fc4c11b4d2c83b179409083ca36fcb95e44b7d8c51abd23e9c07f4d3be8a339c +size 52626 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png index b6c04763dc..9a129e53f4 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0534ab33ee18f03d219eb5b1490d41ccd1e3a4eb6bf734f31383a75110e368b3 -size 63084 +oid sha256:7e8b65396dedff81056157620e2390c8d69954ee288266b575ac61aba16c2bec +size 62590 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png index b86ae23e63..2ac2e539b3 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85971dfed5e1cb01f5b04f43cc998d9cb69b6f06a24e3ea28f32c74aa3445e94 -size 63755 +oid sha256:b8e1fb3b8b62ea8583a5bc9a18f39dbe71684e7d019bf63b22b873851b219209 +size 63270 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png index f1e6084199..f932a63f92 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5e587b7c669fa8fd02b8202318cd405b6fbb3bc91928d56f4bf2ef12f3bbbcc -size 61863 +oid sha256:9ff5ca08d441240de9e5adc35e41bceeb0f462979777345da4b2865e9a80012e +size 61405 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png index 890440ff1b..bcc1e494e8 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a25dcf4e97dddb032713e49e13f46836fd541382aad2a9aa9b683dbcfdd93ccc -size 62409 +oid sha256:2f35b63ac49c799a5b3b5ad47652fe0f199efbbd2a285f782170c1edcd9ae723 +size 61955 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_0_en.png index cbf7bc5ea6..71e06aee74 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed823d5ab8f1e0b1af536c240b2d50ba741de64d443e54d998a4e2d02e373e05 -size 16202 +oid sha256:4d62c0c47ea78b89611244bb6e37ffdf2298b7b161de69ede59871089bd946c1 +size 16617 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_1_en.png index 8dfa3132e2..e1f8d9a655 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:026c69cb2b201c1d753cd6197cdc124e208a3d3c9290d40e4de840ba0ac41cc0 -size 13054 +oid sha256:efab4a3c85b9f762647c5e577c23a49f8ff40fd754171c90b670313f4790cdcd +size 13063 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_2_en.png index 0f72c0ecfd..0e6921fcef 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0da8a3a836e8b3eebb48d00fa2b5080caf80a32579d775bebd7564a525423460 -size 9019 +oid sha256:4a5255962b310c60c62192391f4bf184955827022687f74efa21afda623e0b80 +size 8902 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png index 8fd804cdb1..ce6472cf85 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8b5298b33db0c0e74c6a786598b1826767f5ba3bcf9187846b5b9332b81a3e3 -size 23146 +oid sha256:8be1a1667726344d330d52b8b4844d9007d291b8f2a3aaf1e296a16be98c6b2d +size 23741 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png index 1a7bf98e7c..fd8068cecc 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7607784f41e0e7f6b4b1b4c983ed59aa466b01dd388a149b070a0540f6493d7f -size 18065 +oid sha256:ba2b344818e0b8d4b9224639be09c06de8585622be8dda20ad2aa1bb28e0e44d +size 18052 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_5_en.png index 886a3cbb51..0743b882eb 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d234be8428b62e19b4192a17f1c9c21eeee25c7fc683f1597631b0599fc34b32 -size 13625 +oid sha256:8ab0601bf05f66e91b38774cb77b744bd426a2b083ab43b930a402e3c2b9ddb1 +size 13486 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_6_en.png index 3ec3a1ec64..744a626c00 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a725468cea9dfbf210d7367277b3a4d30438caf4ace60304e9942e4509c71972 -size 34014 +oid sha256:a53eb404b797a91747a92a1fa2ae9ca23cbecda6c8d96991b38c28bb43cb51dc +size 33603 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png index 182ebc51a3..36f2c6a3fe 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c325ae3939648b0684e3ce2105e46445359748bf5d765593fa5eaca5d7e7082 -size 38852 +oid sha256:aabda293c6a242618e176b402e0d5bf8d84f1c6a75d3537b8e42f3abe2f68212 +size 38567 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_8_en.png index 3d321a6803..3ac82460d8 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82ea9e352708d800c28d2651220294c1a12bd09b98cda6676a14680c337f3b6e -size 11094 +oid sha256:bea4702ff62ea222a2ac4a2801b0fb0dc603b8e9f2ce97843d927b21f98d7994 +size 11136 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_0_en.png index 272b05e17c..69be21bda0 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85f42e6853aac517879a0d0379e655c7a23526e8e8a44f5c806323239df65639 -size 15810 +oid sha256:821e281a6bcbb637e713b31fdb6e8bf3b30eef41507d25adb84d9bed4d6d9be0 +size 16083 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_1_en.png index 3d058f4aee..8298f1cbb9 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:288b0d6fbce8b5e90bca734635705217c01bbb2e82f516d66cd0d0cb5f854f94 -size 12568 +oid sha256:4b78de1a677347827c1447cb459c76a20398846f4f96430b97ef3c3024d6b5b4 +size 12519 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_2_en.png index 8573a7aee5..866915721c 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f931739c849cb283722149f9d89286633d7321d78b013281bb3333ae6af9cfd -size 9122 +oid sha256:8cc0851e3ea07010cebc83ac764239e9ab6e37ab2a2ad74039e542f117bf08b9 +size 9110 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png index c66c02a71d..3c6aaa8312 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35dcf251e3bb3613c1cf5fcc4bfed6b8bb837dd721404ac80b72d804830cd483 -size 22375 +oid sha256:3c41209aa36b563a8cd8c5a6788612d17464524b930ca8591e9e29b581a520de +size 22747 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png index fe91d6ff20..3da4e3172e 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:283a7f62058a6f4d29b94a7a0d35b381a3e5038cb59bf545179b807f005aa3dd -size 17240 +oid sha256:53cc573decde4598fcc2c02cd6c7a925af464e215e8589bba71923cb54c0c687 +size 17148 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_5_en.png index be21386b79..2c60ca6e31 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fb4b4a6588f44d637f770456ca621e2c47bb9e730a8d24ace74028b74056309 -size 13032 +oid sha256:c8d2611a87e3b9804c0538d4f3d7988f39f37f038b2a4b0c66f0676b4d03a1a9 +size 13017 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_6_en.png index 6cfb8603b1..bbacbbf72a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:083774bc84a79daac071eac0fca284ce49af7fb3ae21c141684fb588bd8842ed -size 32868 +oid sha256:c8f58003ba5ee7357c33a8e8d995f172ee12f50a6d07fadd6dbc3423bc357272 +size 32582 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png index 8304ff65d9..abf335a860 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4430144e9fb2637619b0fc9cd41ef5a3be7b5a3af7831caaba8bdeb9b96555ae -size 37560 +oid sha256:f6c3d7f0f259fff0089dd4d9f1b9071f43de8e21d5039fe95ab7b9cc37e69a00 +size 37269 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_8_en.png index 1653879613..4e1c38866f 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e68cc7f49af74626e7019b2a4eeac37877b94f0bcba31a21fa3e20b6fde1244d -size 10724 +oid sha256:f1f195d869be456f5eeaf9a6395f7d6e16b7449612c75b2493fcf9ff003ef512 +size 10810 From d8129e72bc0e0ad20e90c3c57d7d9a36b9a6318c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 17:04:48 +0200 Subject: [PATCH 09/13] Let notifications uses the brandColor. --- enterprise | 2 +- .../enterprise/api/EnterpriseService.kt | 3 ++ .../impl/DefaultEnterpriseService.kt | 5 ++ .../enterprise/test/FakeEnterpriseService.kt | 6 +++ .../android/libraries/matrix/test/TestData.kt | 2 + libraries/push/impl/build.gradle.kts | 2 + .../notifications/NotificationDataFactory.kt | 45 ++++++++++++---- .../notifications/NotificationRenderer.kt | 16 ++++-- .../notifications/RoomGroupMessageCreator.kt | 40 +++++++------- .../SummaryGroupMessageCreator.kt | 6 ++- .../factories/NotificationCreator.kt | 54 +++++++++++-------- .../impl/troubleshoot/NotificationTest.kt | 14 +++-- .../DefaultBaseRoomGroupMessageCreatorTest.kt | 7 +++ .../DefaultNotificationDrawerManagerTest.kt | 2 + ...aultOnMissedCallNotificationHandlerTest.kt | 2 + .../DefaultSummaryGroupMessageCreatorTest.kt | 2 + .../NotificationDataFactoryTest.kt | 30 ++++++----- .../notifications/NotificationRendererTest.kt | 2 + .../DefaultNotificationCreatorTest.kt | 24 ++++++--- .../fake/FakeNotificationCreator.kt | 26 ++++++--- .../fake/FakeNotificationDataFactory.kt | 26 +++++++-- .../fake/FakeRoomGroupMessageCreator.kt | 6 ++- .../fake/FakeSummaryGroupMessageCreator.kt | 5 +- .../impl/troubleshoot/NotificationTestTest.kt | 4 ++ 24 files changed, 239 insertions(+), 92 deletions(-) diff --git a/enterprise b/enterprise index 867d1118e1..a3e54addf0 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit 867d1118e157ba89a4f5462f8d9c13e206f10026 +Subproject commit a3e54addf0d61189bb6868f9b5dc733a6a1fb3ea diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt index 0c855c3a82..785afd115a 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt @@ -7,6 +7,7 @@ package io.element.android.features.enterprise.api +import androidx.compose.ui.graphics.Color import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.flow.Flow @@ -24,6 +25,8 @@ interface EnterpriseService { */ suspend fun overrideBrandColor(sessionId: SessionId?, brandColor: String?) + fun brandColorsFlow(sessionId: SessionId?): Flow + fun semanticColorsFlow(sessionId: SessionId?): Flow fun firebasePushGateway(): String? diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt index 7207654bd2..b53e30c080 100644 --- a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt +++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt @@ -7,6 +7,7 @@ package io.element.android.features.enterprise.impl +import androidx.compose.ui.graphics.Color import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import io.element.android.compound.colors.SemanticColorsLightDark @@ -27,6 +28,10 @@ class DefaultEnterpriseService : EnterpriseService { override suspend fun overrideBrandColor(sessionId: SessionId?, brandColor: String?) = Unit + override fun brandColorsFlow(sessionId: SessionId?): Flow { + return flowOf(null) + } + override fun semanticColorsFlow(sessionId: SessionId?): Flow { return flowOf(SemanticColorsLightDark.default) } diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt index 2aedd1edbd..39b7c320d9 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt @@ -7,6 +7,7 @@ package io.element.android.features.enterprise.test +import androidx.compose.ui.graphics.Color import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService @@ -27,6 +28,7 @@ class FakeEnterpriseService( private val firebasePushGatewayResult: () -> String? = { lambdaError() }, private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() }, ) : EnterpriseService { + private val brandColorState = MutableStateFlow(null) private val semanticColorsState = MutableStateFlow(initialSemanticColors) override suspend fun isEnterpriseUser(sessionId: SessionId): Boolean = simulateLongTask { @@ -45,6 +47,10 @@ class FakeEnterpriseService( overrideBrandColorResult(sessionId, brandColor) } + override fun brandColorsFlow(sessionId: SessionId?): Flow { + return brandColorState.asStateFlow() + } + override fun semanticColorsFlow(sessionId: SessionId?): Flow { return semanticColorsState.asStateFlow() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index f05f6958c0..8db5fc6807 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -98,3 +98,5 @@ const val A_TIMESTAMP = 567L const val A_FORMATTED_DATE = "April 6, 1980 at 6:35 PM" const val A_LOGIN_HINT = "mxid:@alice:example.org" + +const val A_COLOR_INT = 0xFF0000 diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index 3d053a4657..87b3c681f1 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation(projects.libraries.troubleshoot.api) implementation(projects.libraries.workmanager.api) implementation(projects.features.call.api) + implementation(projects.features.enterprise.api) implementation(projects.features.lockscreen.api) implementation(projects.libraries.featureflag.api) api(projects.libraries.pushproviders.api) @@ -77,6 +78,7 @@ dependencies { testImplementation(projects.libraries.troubleshoot.test) testImplementation(projects.libraries.workmanager.test) testImplementation(projects.features.call.test) + testImplementation(projects.features.enterprise.test) testImplementation(projects.features.lockscreen.test) testImplementation(projects.features.networkmonitor.test) testImplementation(projects.services.appnavstate.test) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt index 09fc7058dc..38ab52e084 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import android.graphics.Typeface import android.text.style.StyleSpan +import androidx.annotation.ColorInt import androidx.core.text.buildSpannedString import androidx.core.text.inSpans import coil3.ImageLoader @@ -31,17 +32,29 @@ interface NotificationDataFactory { messages: List, currentUser: MatrixUser, imageLoader: ImageLoader, + @ColorInt color: Int, ): List @JvmName("toNotificationInvites") @Suppress("INAPPLICABLE_JVM_NAME") - fun toNotifications(invites: List): List + fun toNotifications( + invites: List, + @ColorInt color: Int, + ): List + @JvmName("toNotificationSimpleEvents") @Suppress("INAPPLICABLE_JVM_NAME") - fun toNotifications(simpleEvents: List): List + fun toNotifications( + simpleEvents: List, + @ColorInt color: Int, + ): List + @JvmName("toNotificationFallbackEvents") @Suppress("INAPPLICABLE_JVM_NAME") - fun toNotifications(fallback: List): List + fun toNotifications( + fallback: List, + @ColorInt color: Int, + ): List fun createSummaryNotification( currentUser: MatrixUser, @@ -49,6 +62,7 @@ interface NotificationDataFactory { invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + @ColorInt color: Int, ): SummaryNotification } @@ -64,6 +78,7 @@ class DefaultNotificationDataFactory( messages: List, currentUser: MatrixUser, imageLoader: ImageLoader, + @ColorInt color: Int, ): List { val messagesToDisplay = messages.filterNot { it.canNotBeDisplayed() } .groupBy { it.roomId } @@ -76,6 +91,7 @@ class DefaultNotificationDataFactory( roomId = roomId, imageLoader = imageLoader, existingNotification = getExistingNotificationForMessages(currentUser.userId, roomId), + color = color, ) RoomNotification( notification = notification, @@ -96,11 +112,14 @@ class DefaultNotificationDataFactory( @JvmName("toNotificationInvites") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(invites: List): List { + override fun toNotifications( + invites: List, + @ColorInt color: Int, + ): List { return invites.map { event -> OneShotNotification( key = event.roomId.value, - notification = notificationCreator.createRoomInvitationNotification(event), + notification = notificationCreator.createRoomInvitationNotification(event, color), summaryLine = event.description, isNoisy = event.noisy, timestamp = event.timestamp @@ -110,11 +129,14 @@ class DefaultNotificationDataFactory( @JvmName("toNotificationSimpleEvents") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(simpleEvents: List): List { + override fun toNotifications( + simpleEvents: List, + @ColorInt color: Int, + ): List { return simpleEvents.map { event -> OneShotNotification( key = event.eventId.value, - notification = notificationCreator.createSimpleEventNotification(event), + notification = notificationCreator.createSimpleEventNotification(event, color), summaryLine = event.description, isNoisy = event.noisy, timestamp = event.timestamp @@ -124,11 +146,14 @@ class DefaultNotificationDataFactory( @JvmName("toNotificationFallbackEvents") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(fallback: List): List { + override fun toNotifications( + fallback: List, + @ColorInt color: Int, + ): List { return fallback.map { event -> OneShotNotification( key = event.eventId.value, - notification = notificationCreator.createFallbackNotification(event), + notification = notificationCreator.createFallbackNotification(event, color), summaryLine = event.description.orEmpty(), isNoisy = false, timestamp = event.timestamp @@ -142,6 +167,7 @@ class DefaultNotificationDataFactory( invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + @ColorInt color: Int, ): SummaryNotification { return when { roomNotifications.isEmpty() && invitationNotifications.isEmpty() && simpleNotifications.isEmpty() -> SummaryNotification.Removed @@ -152,6 +178,7 @@ class DefaultNotificationDataFactory( invitationNotifications = invitationNotifications, simpleNotifications = simpleNotifications, fallbackNotifications = fallbackNotifications, + color = color, ) ) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt index 5d901abbc9..c70148ca25 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt @@ -7,8 +7,11 @@ package io.element.android.libraries.push.impl.notifications +import androidx.compose.ui.graphics.toArgb import coil3.ImageLoader import dev.zacsweers.metro.Inject +import io.element.android.appconfig.NotificationConfig +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.api.notifications.NotificationIdProvider @@ -18,6 +21,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent +import kotlinx.coroutines.flow.first import timber.log.Timber private val loggerTag = LoggerTag("NotificationRenderer", LoggerTag.NotificationLoggerTag) @@ -26,6 +30,7 @@ private val loggerTag = LoggerTag("NotificationRenderer", LoggerTag.Notification class NotificationRenderer( private val notificationDisplayer: NotificationDisplayer, private val notificationDataFactory: NotificationDataFactory, + private val enterpriseService: EnterpriseService, ) { suspend fun render( currentUser: MatrixUser, @@ -33,17 +38,20 @@ class NotificationRenderer( eventsToProcess: List, imageLoader: ImageLoader, ) { + val color = enterpriseService.brandColorsFlow(currentUser.userId).first()?.toArgb() + ?: NotificationConfig.NOTIFICATION_ACCENT_COLOR val groupedEvents = eventsToProcess.groupByType() - val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, currentUser, imageLoader) - val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents) - val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents) - val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents) + val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, currentUser, imageLoader, color) + val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents, color) + val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents, color) + val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents, color) val summaryNotification = notificationDataFactory.createSummaryNotification( currentUser = currentUser, roomNotifications = roomNotifications, invitationNotifications = invitationNotifications, simpleNotifications = simpleNotifications, fallbackNotifications = fallbackNotifications, + color = color, ) // Remove summary first to avoid briefly displaying it after dismissing the last notification diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt index cd75424225..bf6ac73522 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import android.graphics.Bitmap +import androidx.annotation.ColorInt import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding @@ -28,6 +29,7 @@ interface RoomGroupMessageCreator { roomId: RoomId, imageLoader: ImageLoader, existingNotification: Notification?, + @ColorInt color: Int, ): Notification } @@ -43,6 +45,7 @@ class DefaultRoomGroupMessageCreator( roomId: RoomId, imageLoader: ImageLoader, existingNotification: Notification?, + @ColorInt color: Int, ): Notification { val lastKnownRoomEvent = events.last() val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderDisambiguatedDisplayName ?: "Room name (${roomId.value.take(8)}…)" @@ -60,24 +63,25 @@ class DefaultRoomGroupMessageCreator( val smartReplyErrors = events.filter { it.isSmartReplyError() } val roomIsDm = !roomIsGroup return notificationCreator.createMessagesListNotification( - RoomEventGroupInfo( - sessionId = currentUser.userId, - roomId = roomId, - roomDisplayName = roomName, - isDm = roomIsDm, - hasSmartReplyError = smartReplyErrors.isNotEmpty(), - shouldBing = events.any { it.noisy }, - customSound = events.last().soundName, - isUpdated = events.last().isUpdated, - ), - threadId = lastKnownRoomEvent.threadId, - largeIcon = largeBitmap, - lastMessageTimestamp = lastMessageTimestamp, - tickerText = tickerText, - currentUser = currentUser, - existingNotification = existingNotification, - imageLoader = imageLoader, - events = events, + RoomEventGroupInfo( + sessionId = currentUser.userId, + roomId = roomId, + roomDisplayName = roomName, + isDm = roomIsDm, + hasSmartReplyError = smartReplyErrors.isNotEmpty(), + shouldBing = events.any { it.noisy }, + customSound = events.last().soundName, + isUpdated = events.last().isUpdated, + ), + threadId = lastKnownRoomEvent.threadId, + largeIcon = largeBitmap, + lastMessageTimestamp = lastMessageTimestamp, + tickerText = tickerText, + currentUser = currentUser, + existingNotification = existingNotification, + imageLoader = imageLoader, + events = events, + color = color, ) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt index f217b303aa..85947226f1 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification +import androidx.annotation.ColorInt import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import io.element.android.libraries.matrix.api.user.MatrixUser @@ -22,6 +23,7 @@ interface SummaryGroupMessageCreator { invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + @ColorInt color: Int, ): Notification } @@ -45,6 +47,7 @@ class DefaultSummaryGroupMessageCreator( invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + @ColorInt color: Int, ): Notification { val summaryIsNoisy = roomNotifications.any { it.shouldBing } || invitationNotifications.any { it.isNoisy } || @@ -61,7 +64,8 @@ class DefaultSummaryGroupMessageCreator( currentUser, sumTitle, noisy = summaryIsNoisy, - lastMessageTimestamp = lastMessageTimestamp + lastMessageTimestamp = lastMessageTimestamp, + color = color, ) } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index ab2dae4fb3..8e5605ccaf 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -11,6 +11,7 @@ import android.app.Notification import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas +import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.MessagingStyle @@ -19,7 +20,6 @@ import androidx.core.content.res.ResourcesCompat import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.di.annotations.ApplicationContext @@ -57,18 +57,22 @@ interface NotificationCreator { existingNotification: Notification?, imageLoader: ImageLoader, events: List, + @ColorInt color: Int, ): Notification fun createRoomInvitationNotification( - inviteNotifiableEvent: InviteNotifiableEvent + inviteNotifiableEvent: InviteNotifiableEvent, + @ColorInt color: Int, ): Notification fun createSimpleEventNotification( simpleNotifiableEvent: SimpleNotifiableEvent, + @ColorInt color: Int, ): Notification fun createFallbackNotification( fallbackNotifiableEvent: FallbackNotifiableEvent, + @ColorInt color: Int, ): Notification /** @@ -78,10 +82,13 @@ interface NotificationCreator { currentUser: MatrixUser, compatSummary: String, noisy: Boolean, - lastMessageTimestamp: Long + lastMessageTimestamp: Long, + @ColorInt color: Int, ): Notification - fun createDiagnosticNotification(): Notification + fun createDiagnosticNotification( + @ColorInt color: Int, + ): Notification } @ContributesBinding(AppScope::class) @@ -97,8 +104,6 @@ class DefaultNotificationCreator( private val acceptInvitationActionFactory: AcceptInvitationActionFactory, private val rejectInvitationActionFactory: RejectInvitationActionFactory ) : NotificationCreator { - private val accentColor = NotificationConfig.NOTIFICATION_ACCENT_COLOR - /** * Create a notification for a Room. */ @@ -112,15 +117,14 @@ class DefaultNotificationCreator( existingNotification: Notification?, imageLoader: ImageLoader, events: List, + @ColorInt color: Int, ): Notification { // Build the pending intent for when the notification is clicked val openIntent = when { threadId != null -> pendingIntentFactory.createOpenThreadPendingIntent(roomInfo, threadId) else -> pendingIntentFactory.createOpenRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId) } - val smallIcon = CommonDrawables.ic_notification - val containsMissedCall = events.any { it.type == EventType.RTC_NOTIFICATION } val channelId = if (containsMissedCall) { notificationChannels.getChannelForIncomingCall(false) @@ -176,7 +180,7 @@ class DefaultNotificationCreator( ) .setSmallIcon(smallIcon) // Set primary color (important for Wear 2.0 Notifications). - .setColor(accentColor) + .setColor(color) // Sets priority for 25 and below. For 26 and above, 'priority' is deprecated for // 'importance' which is set in the NotificationChannel. The integers representing // 'priority' are different from 'importance', so make sure you don't mix them. @@ -189,7 +193,7 @@ class DefaultNotificationCreator( setSound(it) } */ - setLights(accentColor, 500, 500) + setLights(color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } @@ -221,7 +225,8 @@ class DefaultNotificationCreator( } override fun createRoomInvitationNotification( - inviteNotifiableEvent: InviteNotifiableEvent + inviteNotifiableEvent: InviteNotifiableEvent, + @ColorInt color: Int, ): Notification { val smallIcon = CommonDrawables.ic_notification val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy) @@ -232,7 +237,7 @@ class DefaultNotificationCreator( .setGroup(inviteNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) - .setColor(accentColor) + .setColor(color) .apply { addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent)) addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent)) @@ -247,7 +252,7 @@ class DefaultNotificationCreator( setSound(it) } */ - setLights(accentColor, 500, 500) + setLights(color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } @@ -264,9 +269,9 @@ class DefaultNotificationCreator( override fun createSimpleEventNotification( simpleNotifiableEvent: SimpleNotifiableEvent, + @ColorInt color: Int, ): Notification { val smallIcon = CommonDrawables.ic_notification - val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy) return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) @@ -275,7 +280,7 @@ class DefaultNotificationCreator( .setGroup(simpleNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) - .setColor(accentColor) + .setColor(color) .setAutoCancel(true) .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(simpleNotifiableEvent.sessionId, simpleNotifiableEvent.roomId)) .apply { @@ -287,7 +292,7 @@ class DefaultNotificationCreator( setSound(it) } */ - setLights(accentColor, 500, 500) + setLights(color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } @@ -297,9 +302,9 @@ class DefaultNotificationCreator( override fun createFallbackNotification( fallbackNotifiableEvent: FallbackNotifiableEvent, + @ColorInt color: Int, ): Notification { val smallIcon = CommonDrawables.ic_notification - val channelId = notificationChannels.getChannelIdForMessage(false) return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) @@ -308,7 +313,7 @@ class DefaultNotificationCreator( .setGroup(fallbackNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) - .setColor(accentColor) + .setColor(color) .setAutoCancel(true) .setWhen(fallbackNotifiableEvent.timestamp) // Ideally we'd use `createOpenRoomPendingIntent` here, but the broken notification might apply to an invite @@ -332,7 +337,8 @@ class DefaultNotificationCreator( currentUser: MatrixUser, compatSummary: String, noisy: Boolean, - lastMessageTimestamp: Long + lastMessageTimestamp: Long, + @ColorInt color: Int, ): Notification { val smallIcon = CommonDrawables.ic_notification val channelId = notificationChannels.getChannelIdForMessage(noisy) @@ -345,7 +351,7 @@ class DefaultNotificationCreator( .setGroup(currentUser.userId.value) // set this notification as the summary for the group .setGroupSummary(true) - .setColor(accentColor) + .setColor(color) .apply { if (noisy) { // Compat @@ -355,7 +361,7 @@ class DefaultNotificationCreator( setSound(it) } */ - setLights(accentColor, 500, 500) + setLights(color, 500, 500) } else { // compat priority = NotificationCompat.PRIORITY_LOW @@ -366,14 +372,16 @@ class DefaultNotificationCreator( .build() } - override fun createDiagnosticNotification(): Notification { + override fun createDiagnosticNotification( + @ColorInt color: Int, + ): Notification { val intent = pendingIntentFactory.createTestPendingIntent() return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest()) .setContentTitle(buildMeta.applicationName) .setContentText(stringProvider.getString(R.string.notification_test_push_notification_content)) .setSmallIcon(CommonDrawables.ic_notification) .setLargeIcon(getBitmap(R.drawable.element_logo_green)) - .setColor(accentColor) + .setColor(color) .setPriority(NotificationCompat.PRIORITY_MAX) .setCategory(NotificationCompat.CATEGORY_STATUS) .setAutoCancel(true) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt index da390f85f2..b217247aa2 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt @@ -7,9 +7,13 @@ package io.element.android.libraries.push.impl.troubleshoot -import dev.zacsweers.metro.AppScope +import androidx.compose.ui.graphics.toArgb import dev.zacsweers.metro.ContributesIntoSet import dev.zacsweers.metro.Inject +import io.element.android.appconfig.NotificationConfig +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationDisplayer import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator @@ -25,13 +29,15 @@ import kotlinx.coroutines.withTimeout import timber.log.Timber import kotlin.time.Duration.Companion.seconds -@ContributesIntoSet(AppScope::class) +@ContributesIntoSet(SessionScope::class) @Inject class NotificationTest( + private val sessionId: SessionId, private val notificationCreator: NotificationCreator, private val notificationDisplayer: NotificationDisplayer, private val notificationClickHandler: NotificationClickHandler, private val stringProvider: StringProvider, + private val enterpriseService: EnterpriseService, ) : NotificationTroubleshootTest { override val order = 50 private val delegate = NotificationTroubleshootTestDelegate( @@ -43,7 +49,9 @@ class NotificationTest( override suspend fun run(coroutineScope: CoroutineScope) { delegate.start() - val notification = notificationCreator.createDiagnosticNotification() + val color = enterpriseService.brandColorsFlow(sessionId).first()?.toArgb() + ?: NotificationConfig.NOTIFICATION_ACCENT_COLOR + val notification = notificationCreator.createDiagnosticNotification(color) val result = notificationDisplayer.displayDiagnosticNotification(notification) if (result) { coroutineScope.listenToNotificationClick() diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt index ba4e1657a7..ad872cfd1d 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt @@ -13,6 +13,7 @@ import androidx.core.app.NotificationCompat import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.test.A_COLOR_INT import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_TIMESTAMP import io.element.android.libraries.matrix.ui.components.aMatrixUser @@ -52,6 +53,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { roomId = A_ROOM_ID, imageLoader = fakeImageLoader.getImageLoader(), existingNotification = null, + color = A_COLOR_INT, ) assertThat(result.number).isEqualTo(1) @Suppress("DEPRECATION") @@ -74,6 +76,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { roomId = A_ROOM_ID, imageLoader = fakeImageLoader.getImageLoader(), existingNotification = null, + color = A_COLOR_INT, ) @Suppress("DEPRECATION") assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_DEFAULT) @@ -138,6 +141,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { roomId = A_ROOM_ID, imageLoader = fakeImageLoader.getImageLoader(), existingNotification = null, + color = A_COLOR_INT, ) assertThat(result.number).isEqualTo(1) assertThat(fakeImageLoader.getCoilRequests()).containsExactlyElementsIn(expectedCoilRequests) @@ -156,6 +160,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { roomId = A_ROOM_ID, imageLoader = fakeImageLoader.getImageLoader(), existingNotification = null, + color = A_COLOR_INT, ) assertThat(result.number).isEqualTo(2) assertThat(result.`when`).isEqualTo(A_TIMESTAMP + 10) @@ -184,6 +189,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { roomId = A_ROOM_ID, imageLoader = fakeImageLoader.getImageLoader(), existingNotification = null, + color = A_COLOR_INT, ) val actionTitles = result.actions?.map { it.title } assertThat(actionTitles).isEqualTo( @@ -208,6 +214,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { roomId = A_ROOM_ID, imageLoader = fakeImageLoader.getImageLoader(), existingNotification = null, + color = A_COLOR_INT, ) assertThat(result.number).isEqualTo(1) assertThat(result.`when`).isEqualTo(A_TIMESTAMP) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt index 07702602e8..36c406d129 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import androidx.core.app.NotificationManagerCompat import com.google.common.truth.Truth.assertThat +import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -199,6 +200,7 @@ class DefaultNotificationDrawerManagerTest { activeNotificationsProvider = activeNotificationsProvider, stringProvider = FakeStringProvider(), ), + enterpriseService = FakeEnterpriseService(), ), appNavigationStateService = appNavigationStateService, coroutineScope = this, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt index 25e00f7977..8b668a7f77 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.push.impl.notifications +import io.element.android.features.enterprise.test.FakeEnterpriseService 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 @@ -55,6 +56,7 @@ class DefaultOnMissedCallNotificationHandlerTest { notificationRenderer = NotificationRenderer( notificationDisplayer = FakeNotificationDisplayer(), notificationDataFactory = dataFactory, + enterpriseService = FakeEnterpriseService(), ), appNavigationStateService = FakeAppNavigationStateService(), coroutineScope = backgroundScope, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt index aae742fa6d..f0831f6fd0 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import androidx.core.app.NotificationCompat import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.A_COLOR_INT import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator @@ -47,6 +48,7 @@ class DefaultSummaryGroupMessageCreatorTest { invitationNotifications = emptyList(), simpleNotifications = emptyList(), fallbackNotifications = emptyList(), + color = A_COLOR_INT, ) notificationCreator.createSummaryListNotificationResult.assertions() diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt index 30c433a513..b8f4364ddb 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt @@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.EventId 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_COLOR_INT 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.FakeActiveNotificationsProvider @@ -53,7 +54,7 @@ class NotificationDataFactoryTest { val expectedNotification = notificationCreator.createRoomInvitationNotificationResult(AN_INVITATION_EVENT) val roomInvitation = listOf(AN_INVITATION_EVENT) - val result = toNotifications(roomInvitation) + val result = toNotifications(roomInvitation, A_COLOR_INT) assertThat(result).isEqualTo( listOf( @@ -73,7 +74,7 @@ class NotificationDataFactoryTest { val expectedNotification = notificationCreator.createRoomInvitationNotificationResult(AN_INVITATION_EVENT) val roomInvitation = listOf(A_SIMPLE_EVENT) - val result = toNotifications(roomInvitation) + val result = toNotifications(roomInvitation, A_COLOR_INT) assertThat(result).isEqualTo( listOf( @@ -93,11 +94,12 @@ class NotificationDataFactoryTest { val events = listOf(A_MESSAGE_EVENT) val expectedNotification = RoomNotification( notification = fakeRoomGroupMessageCreator.createRoomMessage( - MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - events, - A_ROOM_ID, - FakeImageLoader().getImageLoader(), - null, + currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + events = events, + roomId = A_ROOM_ID, + imageLoader = FakeImageLoader().getImageLoader(), + existingNotification = null, + color = A_COLOR_INT, ), roomId = A_ROOM_ID, summaryLine = "A room name: Bob Hello world!", @@ -112,6 +114,7 @@ class NotificationDataFactoryTest { messages = roomWithMessage, currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), imageLoader = fakeImageLoader.getImageLoader(), + color = A_COLOR_INT, ) assertThat(result.size).isEqualTo(1) @@ -128,6 +131,7 @@ class NotificationDataFactoryTest { messages = redactedRoom, currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), imageLoader = fakeImageLoader.getImageLoader(), + color = A_COLOR_INT, ) assertThat(result).isEmpty() @@ -145,11 +149,12 @@ class NotificationDataFactoryTest { val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("\$not-redacted"))) val expectedNotification = RoomNotification( notification = fakeRoomGroupMessageCreator.createRoomMessage( - MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - withRedactedRemoved, - A_ROOM_ID, - FakeImageLoader().getImageLoader(), - null, + currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + events = withRedactedRemoved, + roomId = A_ROOM_ID, + imageLoader = FakeImageLoader().getImageLoader(), + existingNotification = null, + color = A_COLOR_INT, ), roomId = A_ROOM_ID, summaryLine = "A room name: Bob Hello world!", @@ -163,6 +168,7 @@ class NotificationDataFactoryTest { messages = roomWithRedactedMessage, currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), imageLoader = fakeImageLoader.getImageLoader(), + color = A_COLOR_INT, ) assertThat(result.size).isEqualTo(1) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt index 8912693bc4..069aaf22ba 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.push.impl.notifications +import io.element.android.features.enterprise.test.FakeEnterpriseService 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 @@ -58,6 +59,7 @@ class NotificationRendererTest { private val notificationRenderer = NotificationRenderer( notificationDisplayer = notificationDisplayer, notificationDataFactory = notificationDataFactory, + enterpriseService = FakeEnterpriseService(), ) @Test diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt index 7786fb261e..06283a76d0 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt @@ -16,6 +16,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_COLOR_INT 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_THREAD_ID @@ -50,7 +51,9 @@ class DefaultNotificationCreatorTest { @Test fun `test createDiagnosticNotification`() { val sut = createNotificationCreator() - val result = sut.createDiagnosticNotification() + val result = sut.createDiagnosticNotification( + color = A_COLOR_INT, + ) result.commonAssertions( expectedGroup = null, expectedCategory = NotificationCompat.CATEGORY_STATUS, @@ -72,7 +75,8 @@ class DefaultNotificationCreatorTest { isUpdated = false, timestamp = A_FAKE_TIMESTAMP, cause = null, - ) + ), + color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -97,7 +101,8 @@ class DefaultNotificationCreatorTest { canBeReplaced = false, isRedacted = false, isUpdated = false, - ) + ), + color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -122,7 +127,8 @@ class DefaultNotificationCreatorTest { canBeReplaced = false, isRedacted = false, isUpdated = false, - ) + ), + color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -148,7 +154,8 @@ class DefaultNotificationCreatorTest { isRedacted = false, isUpdated = false, roomName = "roomName", - ) + ), + color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -181,7 +188,8 @@ class DefaultNotificationCreatorTest { isRedacted = false, isUpdated = false, roomName = "roomName", - ) + ), + color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -197,6 +205,7 @@ class DefaultNotificationCreatorTest { compatSummary = "compatSummary", noisy = false, lastMessageTimestamp = 123_456L, + color = A_COLOR_INT, ) result.commonAssertions( expectedGroup = matrixUser.userId.value, @@ -212,6 +221,7 @@ class DefaultNotificationCreatorTest { compatSummary = "compatSummary", noisy = true, lastMessageTimestamp = 123_456L, + color = A_COLOR_INT, ) result.commonAssertions( expectedGroup = matrixUser.userId.value, @@ -240,6 +250,7 @@ class DefaultNotificationCreatorTest { existingNotification = null, imageLoader = FakeImageLoader().getImageLoader(), events = emptyList(), + color = A_COLOR_INT, ) result.commonAssertions() } @@ -266,6 +277,7 @@ class DefaultNotificationCreatorTest { existingNotification = null, imageLoader = FakeImageLoader().getImageLoader(), events = emptyList(), + color = A_COLOR_INT, ) result.commonAssertions() } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt index 3cbed6e8bc..491b0bac33 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.push.impl.notifications.fake import android.app.Notification import android.graphics.Bitmap +import androidx.annotation.ColorInt import coil3.ImageLoader import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.user.MatrixUser @@ -44,22 +45,32 @@ class FakeNotificationCreator( currentUser: MatrixUser, existingNotification: Notification?, imageLoader: ImageLoader, - events: List + events: List, + @ColorInt color: Int, ): Notification { return createMessagesListNotificationResult( listOf(roomInfo, threadId, largeIcon, lastMessageTimestamp, tickerText, currentUser, existingNotification, imageLoader, events) ) } - override fun createRoomInvitationNotification(inviteNotifiableEvent: InviteNotifiableEvent): Notification { + override fun createRoomInvitationNotification( + inviteNotifiableEvent: InviteNotifiableEvent, + @ColorInt color: Int, + ): Notification { return createRoomInvitationNotificationResult(inviteNotifiableEvent) } - override fun createSimpleEventNotification(simpleNotifiableEvent: SimpleNotifiableEvent): Notification { + override fun createSimpleEventNotification( + simpleNotifiableEvent: SimpleNotifiableEvent, + @ColorInt color: Int, + ): Notification { return createSimpleNotificationResult(simpleNotifiableEvent) } - override fun createFallbackNotification(fallbackNotifiableEvent: FallbackNotifiableEvent): Notification { + override fun createFallbackNotification( + fallbackNotifiableEvent: FallbackNotifiableEvent, + @ColorInt color: Int, + ): Notification { return createFallbackNotificationResult(fallbackNotifiableEvent) } @@ -67,12 +78,15 @@ class FakeNotificationCreator( currentUser: MatrixUser, compatSummary: String, noisy: Boolean, - lastMessageTimestamp: Long + lastMessageTimestamp: Long, + @ColorInt color: Int, ): Notification { return createSummaryListNotificationResult(currentUser, compatSummary, noisy, lastMessageTimestamp) } - override fun createDiagnosticNotification(): Notification { + override fun createDiagnosticNotification( + @ColorInt color: Int, + ): Notification { return createDiagnosticNotificationResult() } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt index fed6e3c7a3..9a0a5fe7ef 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.push.impl.notifications.fake +import androidx.annotation.ColorInt import coil3.ImageLoader import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.NotificationDataFactory @@ -33,31 +34,45 @@ class FakeNotificationDataFactory( List, List, SummaryNotification - > = lambdaRecorder { _, _, _, _, _ -> SummaryNotification.Update(A_NOTIFICATION) }, + > = lambdaRecorder { _, _, _, _, _ -> SummaryNotification.Update(A_NOTIFICATION) }, var inviteToNotificationsResult: LambdaOneParamRecorder, List> = lambdaRecorder { _ -> emptyList() }, var simpleEventToNotificationsResult: LambdaOneParamRecorder, List> = lambdaRecorder { _ -> emptyList() }, var fallbackEventToNotificationsResult: LambdaOneParamRecorder, List> = lambdaRecorder { _ -> emptyList() }, ) : NotificationDataFactory { - override suspend fun toNotifications(messages: List, currentUser: MatrixUser, imageLoader: ImageLoader): List { + override suspend fun toNotifications( + messages: List, + currentUser: MatrixUser, + imageLoader: ImageLoader, + @ColorInt color: Int, + ): List { return messageEventToNotificationsResult(messages, currentUser, imageLoader) } @JvmName("toNotificationInvites") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(invites: List): List { + override fun toNotifications( + invites: List, + @ColorInt color: Int, + ): List { return inviteToNotificationsResult(invites) } @JvmName("toNotificationSimpleEvents") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(simpleEvents: List): List { + override fun toNotifications( + simpleEvents: List, + @ColorInt color: Int, + ): List { return simpleEventToNotificationsResult(simpleEvents) } @JvmName("toNotificationFallbackEvents") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(fallback: List): List { + override fun toNotifications( + fallback: List, + @ColorInt color: Int, + ): List { return fallbackEventToNotificationsResult(fallback) } @@ -67,6 +82,7 @@ class FakeNotificationDataFactory( invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + @ColorInt color: Int, ): SummaryNotification { return summaryToNotificationsResult( currentUser, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt index c531735a1e..344c77716f 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.push.impl.notifications.fake import android.app.Notification +import androidx.annotation.ColorInt import coil3.ImageLoader import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.user.MatrixUser @@ -19,14 +20,15 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeRoomGroupMessageCreator( var createRoomMessageResult: LambdaFiveParamsRecorder, RoomId, ImageLoader, Notification?, Notification> = - lambdaRecorder { _, _, _, _, _, -> A_NOTIFICATION } + lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION } ) : RoomGroupMessageCreator { override suspend fun createRoomMessage( currentUser: MatrixUser, events: List, roomId: RoomId, imageLoader: ImageLoader, - existingNotification: Notification? + existingNotification: Notification?, + @ColorInt color: Int, ): Notification { return createRoomMessageResult(currentUser, events, roomId, imageLoader, existingNotification) } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt index ed3ca3027e..bc8a5515c9 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.push.impl.notifications.fake import android.app.Notification +import androidx.annotation.ColorInt import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.OneShotNotification import io.element.android.libraries.push.impl.notifications.RoomNotification @@ -18,8 +19,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeSummaryGroupMessageCreator( var createSummaryNotificationResult: LambdaFiveParamsRecorder< - MatrixUser, List, List, List, List, Notification - > = + MatrixUser, List, List, List, List, Notification> = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION } ) : SummaryGroupMessageCreator { override fun createSummaryNotification( @@ -28,6 +28,7 @@ class FakeSummaryGroupMessageCreator( invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + @ColorInt color: Int, ): Notification { return createSummaryNotificationResult( currentUser, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt index eecb3801fd..7d94fd0082 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt @@ -8,6 +8,8 @@ package io.element.android.libraries.push.impl.troubleshoot import com.google.common.truth.Truth.assertThat +import io.element.android.features.enterprise.test.FakeEnterpriseService +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState @@ -64,10 +66,12 @@ class NotificationTestTest { private fun createNotificationTest(): NotificationTest { return NotificationTest( + sessionId = A_SESSION_ID, notificationCreator = notificationCreator, notificationDisplayer = fakeNotificationDisplayer, notificationClickHandler = notificationClickHandler, stringProvider = FakeStringProvider(), + enterpriseService = FakeEnterpriseService(), ) } } From 06cf6c321eb7979a39716aac2f13cff981eb6569 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 17:06:03 +0200 Subject: [PATCH 10/13] Remove element_logo_green.xml from the diagnostic notification. --- .../factories/NotificationCreator.kt | 14 ------------ .../drawable-xxhdpi/element_logo_green.xml | 22 ------------------- 2 files changed, 36 deletions(-) delete mode 100644 libraries/push/impl/src/main/res/drawable-xxhdpi/element_logo_green.xml diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index 8e5605ccaf..873137f7c0 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -10,13 +10,10 @@ package io.element.android.libraries.push.impl.notifications.factories import android.app.Notification import android.content.Context import android.graphics.Bitmap -import android.graphics.Canvas import androidx.annotation.ColorInt -import androidx.annotation.DrawableRes import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.MessagingStyle import androidx.core.app.Person -import androidx.core.content.res.ResourcesCompat import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding @@ -380,7 +377,6 @@ class DefaultNotificationCreator( .setContentTitle(buildMeta.applicationName) .setContentText(stringProvider.getString(R.string.notification_test_push_notification_content)) .setSmallIcon(CommonDrawables.ic_notification) - .setLargeIcon(getBitmap(R.drawable.element_logo_green)) .setColor(color) .setPriority(NotificationCompat.PRIORITY_MAX) .setCategory(NotificationCompat.CATEGORY_STATUS) @@ -469,16 +465,6 @@ class DefaultNotificationCreator( } } - private fun getBitmap(@DrawableRes drawableRes: Int): Bitmap? { - val drawable = ResourcesCompat.getDrawable(context.resources, drawableRes, null) ?: return null - val canvas = Canvas() - val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) - canvas.setBitmap(bitmap) - drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) - drawable.draw(canvas) - return bitmap - } - companion object { const val MESSAGE_EVENT_ID = "message_event_id" } diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/element_logo_green.xml b/libraries/push/impl/src/main/res/drawable-xxhdpi/element_logo_green.xml deleted file mode 100644 index e9b119c969..0000000000 --- a/libraries/push/impl/src/main/res/drawable-xxhdpi/element_logo_green.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - From 69b4fdec048a6b7312a134ea9df783ba626874ff Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 24 Oct 2025 18:15:15 +0200 Subject: [PATCH 11/13] Fix ktlint --- .../element/android/features/home/impl/spaces/HomeSpacesView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt index 12ef091b3a..0b8e0bd14b 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt @@ -9,8 +9,8 @@ package io.element.android.features.home.impl.spaces import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter From 0250e6fa75bb8575d262a68d95797812e5f01e61 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 18:13:43 +0200 Subject: [PATCH 12/13] Let notifications uses the brandColor. --- enterprise | 2 +- .../enterprise/impl/DefaultEnterpriseServiceTest.kt | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/enterprise b/enterprise index a3e54addf0..c5249d001c 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit a3e54addf0d61189bb6868f9b5dc733a6a1fb3ea +Subproject commit c5249d001c766261206e145fa2c54d5d9c95f2e6 diff --git a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt index b617f84bdd..92303a189d 100644 --- a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt +++ b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt @@ -51,6 +51,16 @@ class DefaultEnterpriseServiceTest { } } + @Test + fun `brandColorsFlow always emits null`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + defaultEnterpriseService.brandColorsFlow(null).test { + val initialState = awaitItem() + assertThat(initialState).isNull() + awaitComplete() + } + } + @Test fun `semanticColorsFlow always emits the same value for a session`() = runTest { val defaultEnterpriseService = DefaultEnterpriseService() From 10d4c102e0545957efea519c114d462e7106eb0c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 18:29:45 +0200 Subject: [PATCH 13/13] Update ref. --- enterprise | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise b/enterprise index c5249d001c..19d78b589d 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit c5249d001c766261206e145fa2c54d5d9c95f2e6 +Subproject commit 19d78b589dfbca08b1e8306bff1a236fa2cdf528