From 9a555a4dacf1be9451151bc0d52fd27753650a42 Mon Sep 17 00:00:00 2001 From: Samuel Truman Date: Wed, 9 Sep 2026 01:56:08 +0200 Subject: [PATCH] Send to the conversation picked in the share sheet Picking a conversation in the Direct Share row did nothing with that choice: the app still opened the account picker, then the room picker, so the shortcut saved no steps over sharing to Element X itself. The system names the chosen shortcut in Intent.EXTRA_SHORTCUT_ID, which nothing read. The share now carries that id, and the session it belongs to resolves the room it was built for, so the share goes straight there. The id format moves to the push api module, since building and matching it is no longer something only the notification code does. The extra is absent when the app is opened from a launcher shortcut rather than the Direct Share row, and an id that matches no logged in session resolves to nothing, so both fall back to the pickers as before. Part of #3835 Co-Authored-By: Claude Opus 5 --- .../io/element/android/appnav/RootFlowNode.kt | 11 ++++ .../features/share/api/ShareIntentData.kt | 17 +++++- features/share/impl/build.gradle.kts | 1 + .../share/impl/DefaultShareIntentHandler.kt | 11 +++- .../features/share/impl/SharePresenter.kt | 11 ++++ .../impl/DefaultShareIntentHandlerTest.kt | 39 +++++++++++++ .../features/share/impl/SharePresenterTest.kt | 55 +++++++++++++++++++ libraries/push/api/build.gradle.kts | 4 ++ .../conversations/ConversationShortcutId.kt | 28 ++++++++++ .../ConversationShortcutIdTest.kt | 35 ++++++++++++ .../DefaultNotificationConversationService.kt | 21 +++---- .../factories/NotificationCreator.kt | 4 +- .../push/impl/notifications/shortcut/Utils.kt | 3 - ...aultNotificationConversationServiceTest.kt | 4 +- 14 files changed, 220 insertions(+), 24 deletions(-) create mode 100644 libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/conversations/ConversationShortcutId.kt create mode 100644 libraries/push/api/src/test/kotlin/io/element/android/libraries/push/api/notifications/conversations/ConversationShortcutIdTest.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index c9deaeb3b3e..8482203d446 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -65,6 +65,7 @@ import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.oauth.api.OAuthAction import io.element.android.libraries.oauth.api.OAuthActionFlow +import io.element.android.libraries.push.api.notifications.conversations.conversationShortcutRoomId import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.ui.common.nodes.emptyNode @@ -438,6 +439,16 @@ class RootFlowNode( } private suspend fun onIncomingShare(shareIntentData: ShareIntentData) { + // The share sheet named the conversation, so there is no account to choose. + val shortcutSessionId = shareIntentData.shortcutId?.let { shortcutId -> + sessionStore.getAllSessions() + .map { SessionId(it.userId) } + .firstOrNull { conversationShortcutRoomId(shortcutId, it) != null } + } + if (shortcutSessionId != null) { + attachSession(shortcutSessionId).attachIncomingShare(shareIntentData) + return + } // Is there a session already? val latestSessionId = sessionStore.getLatestSessionId() if (latestSessionId == null) { diff --git a/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareIntentData.kt b/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareIntentData.kt index e5407c57d95..200a8c591f6 100644 --- a/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareIntentData.kt +++ b/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareIntentData.kt @@ -15,17 +15,30 @@ import kotlinx.parcelize.Parcelize * Share intent data, mapped from the original [android.content.Intent]. */ sealed interface ShareIntentData : Parcelable { + /** + * The id of the shortcut the user picked in the Direct Share row of the system share sheet, or `null` + * when they shared to the app itself and still have to choose a conversation. + */ + val shortcutId: String? + /** * A list of [Uri]s to share and their mime types, with an optional [text] to be used as caption. */ @Parcelize - data class Uris(val text: String?, val uris: List) : ShareIntentData + data class Uris( + val text: String?, + val uris: List, + override val shortcutId: String? = null, + ) : ShareIntentData /** * A plain text to share. */ @Parcelize - data class PlainText(val content: String) : ShareIntentData + data class PlainText( + val content: String, + override val shortcutId: String? = null, + ) : ShareIntentData } /** diff --git a/features/share/impl/build.gradle.kts b/features/share/impl/build.gradle.kts index 255263cf026..27045ec8bfa 100644 --- a/features/share/impl/build.gradle.kts +++ b/features/share/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.push.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) implementation(projects.libraries.mediaupload.api) diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandler.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandler.kt index 2ae31fb00cd..0b7825bcbac 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandler.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandler.kt @@ -40,8 +40,9 @@ class DefaultShareIntentHandler( ): ShareIntentData? { val type = intent.resolveType(context) ?: return null val uris = getIncomingUris(intent, type) + val shortcutId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID) return when { - uris.isEmpty() && type == MimeTypes.PlainText -> handlePlainText(intent) + uris.isEmpty() && type == MimeTypes.PlainText -> handlePlainText(intent, shortcutId) uris.isNotEmpty() || type.isMimeTypeImage() || type.isMimeTypeVideo() || @@ -53,16 +54,20 @@ class DefaultShareIntentHandler( ShareIntentData.Uris( text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString()?.takeIf { it.isNotEmpty() }, uris = uris, + shortcutId = shortcutId, ) } else -> null } } - private fun handlePlainText(intent: Intent): ShareIntentData.PlainText? { + private fun handlePlainText(intent: Intent, shortcutId: String?): ShareIntentData.PlainText? { val content = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString() return if (content?.isNotEmpty() == true) { - ShareIntentData.PlainText(content) + ShareIntentData.PlainText( + content = content, + shortcutId = shortcutId, + ) } else { null } diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt index 3bd9b982276..b2a37f611d8 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt @@ -9,6 +9,7 @@ package io.element.android.features.share.impl import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import dev.zacsweers.metro.Assisted @@ -26,6 +27,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaSenderRoomFactory +import io.element.android.libraries.push.api.notifications.conversations.conversationShortcutRoomId import io.element.android.services.appnavstate.api.ActiveRoomsHolder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -55,6 +57,15 @@ class SharePresenter( @Composable override fun present(): ShareState { + val pickedRoomId = shareIntentData.shortcutId?.let { conversationShortcutRoomId(it, matrixClient.sessionId) } + LaunchedEffect(Unit) { + // Send to the room picked in the share sheet, unless a configuration change is replaying this + // effect over a send already in flight. Anything else falls back to room selection. + if (pickedRoomId != null && shareActionState.value is AsyncAction.Uninitialized) { + onRoomSelected(listOf(pickedRoomId)) + } + } + fun handleEvent(event: ShareEvent) { when (event) { ShareEvent.ClearError -> shareActionState.value = AsyncAction.Uninitialized diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandlerTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandlerTest.kt index d81bbe053dd..2dad5c05ff5 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandlerTest.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandlerTest.kt @@ -22,6 +22,9 @@ import com.google.common.truth.Truth.assertWithMessage import io.element.android.features.share.api.ShareIntentData import io.element.android.features.share.api.UriToShare import io.element.android.libraries.core.mimetype.MimeTypes +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.api.notifications.conversations.createConversationShortcutId import io.element.android.tests.testutils.robolectric.RobolectricTest import org.junit.Test import org.robolectric.Robolectric @@ -247,6 +250,42 @@ class DefaultShareIntentHandlerTest : RobolectricTest() { ) } + @Test + fun `a conversation picked in the share sheet is carried on the shared text`() { + val intent = Intent(Intent.ACTION_SEND).apply { + type = MimeTypes.PlainText + putExtra(Intent.EXTRA_TEXT, "a message") + putExtra(Intent.EXTRA_SHORTCUT_ID, createConversationShortcutId(A_SESSION_ID, A_ROOM_ID)) + } + + val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent) + + assertThat(result).isEqualTo( + ShareIntentData.PlainText( + content = "a message", + shortcutId = createConversationShortcutId(A_SESSION_ID, A_ROOM_ID), + ) + ) + } + + @Test + fun `a conversation picked in the share sheet is carried on shared uris`() { + val uri = "content://sender/image.jpg".toUri() + val intent = aSendIntent(type = "image/jpeg", uri = uri).apply { + putExtra(Intent.EXTRA_SHORTCUT_ID, createConversationShortcutId(A_SESSION_ID, A_ROOM_ID)) + } + + val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent) + + assertThat(result).isEqualTo( + ShareIntentData.Uris( + text = null, + uris = listOf(UriToShare(uri = uri, mimeType = "image/jpeg")), + shortcutId = createConversationShortcutId(A_SESSION_ID, A_ROOM_ID), + ) + ) + } + private fun aSendIntent(type: String, uri: Uri) = Intent(Intent.ACTION_SEND).apply { this.type = type putExtra(Intent.EXTRA_STREAM, uri) diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt index 44ccea92d6b..030a9563eeb 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt @@ -21,6 +21,8 @@ import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline @@ -28,6 +30,7 @@ import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvi import io.element.android.libraries.mediaupload.api.MediaSenderRoomFactory import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.test.FakeMediaSender +import io.element.android.libraries.push.api.notifications.conversations.createConversationShortcutId import io.element.android.services.appnavstate.api.ActiveRoomsHolder import io.element.android.services.appnavstate.impl.DefaultActiveRoomsHolder import io.element.android.tests.testutils.WarmUpRule @@ -97,6 +100,58 @@ class SharePresenterTest : RobolectricTest() { } } + @Test + fun `present - a room picked in the share sheet is shared to without selecting one`() = runTest { + val joinedRoom = FakeJoinedRoom( + liveTimeline = FakeTimeline().apply { + sendMessageLambda = { _, _, _, _, _ -> Result.success(Unit) } + }, + ) + val matrixClient = FakeMatrixClient().apply { + givenGetRoomResult(A_ROOM_ID, joinedRoom) + } + val presenter = createSharePresenter( + matrixClient = matrixClient, + shareIntentData = ShareIntentData.PlainText( + content = A_MESSAGE, + shortcutId = createConversationShortcutId(A_SESSION_ID, A_ROOM_ID), + ), + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + assertThat(awaitItem().shareAction.isUninitialized()).isTrue() + assertThat(awaitItem().shareAction.isLoading()).isTrue() + assertThat(awaitItem().shareAction).isEqualTo(AsyncAction.Success(listOf(A_ROOM_ID))) + } + } + + @Test + fun `present - a room picked for a session that is no longer attached is not shared to`() = runTest { + val joinedRoom = FakeJoinedRoom( + liveTimeline = FakeTimeline().apply { + sendMessageLambda = { _, _, _, _, _ -> Result.success(Unit) } + }, + ) + val matrixClient = FakeMatrixClient(sessionId = A_SESSION_ID_2).apply { + givenGetRoomResult(A_ROOM_ID, joinedRoom) + } + val presenter = createSharePresenter( + matrixClient = matrixClient, + shareIntentData = ShareIntentData.PlainText( + content = A_MESSAGE, + // Picked for A_SESSION_ID, but A_SESSION_ID_2 is handling the share. + shortcutId = createConversationShortcutId(A_SESSION_ID, A_ROOM_ID), + ), + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + assertThat(awaitItem().shareAction.isUninitialized()).isTrue() + expectNoEvents() + } + } + @Test fun `present - send text ok`() = runTest { val joinedRoom = FakeJoinedRoom( diff --git a/libraries/push/api/build.gradle.kts b/libraries/push/api/build.gradle.kts index 91e545daed6..392d0e93f9f 100644 --- a/libraries/push/api/build.gradle.kts +++ b/libraries/push/api/build.gradle.kts @@ -1,3 +1,5 @@ +import extension.testCommonDependencies + /* * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023-2025 New Vector Ltd. @@ -22,4 +24,6 @@ dependencies { implementation(projects.libraries.matrixmedia.api) implementation(projects.libraries.preferences.api) implementation(projects.libraries.pushproviders.api) + + testCommonDependencies(libs) } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/conversations/ConversationShortcutId.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/conversations/ConversationShortcutId.kt new file mode 100644 index 00000000000..af36021c9ad --- /dev/null +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/conversations/ConversationShortcutId.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026 Element Creations 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.api.notifications.conversations + +import io.element.android.libraries.matrix.api.core.MatrixPatterns +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId + +/** + * Build the id of the conversation shortcut for a room. The id is part of the contract with the system, which + * hands it back in [android.content.Intent.EXTRA_SHORTCUT_ID] when the user shares to that shortcut. + */ +fun createConversationShortcutId(sessionId: SessionId, roomId: RoomId) = "$sessionId-$roomId" + +/** + * The room [shortcutId] was built for, or `null` when it is not the id of a conversation shortcut of [sessionId]. + */ +fun conversationShortcutRoomId(shortcutId: String, sessionId: SessionId): RoomId? { + val prefix = "$sessionId-" + if (!shortcutId.startsWith(prefix)) return null + // Another app can send any id in the share intent, so what follows the prefix is not necessarily a room id. + return shortcutId.removePrefix(prefix).takeIf { MatrixPatterns.isRoomId(it) }?.let(::RoomId) +} diff --git a/libraries/push/api/src/test/kotlin/io/element/android/libraries/push/api/notifications/conversations/ConversationShortcutIdTest.kt b/libraries/push/api/src/test/kotlin/io/element/android/libraries/push/api/notifications/conversations/ConversationShortcutIdTest.kt new file mode 100644 index 00000000000..eff305ba6f9 --- /dev/null +++ b/libraries/push/api/src/test/kotlin/io/element/android/libraries/push/api/notifications/conversations/ConversationShortcutIdTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026 Element Creations 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.api.notifications.conversations + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import org.junit.Test + +class ConversationShortcutIdTest { + @Test + fun `roomId - resolves the room an id was built for, dashes in the domains included`() { + val sessionId = SessionId("@alice:my-server.example.org") + val roomId = RoomId("!a-room:other-server.example.org") + + val result = conversationShortcutRoomId(createConversationShortcutId(sessionId, roomId), sessionId) + + assertThat(result).isEqualTo(roomId) + } + + @Test + fun `roomId - returns null for another session, or for an id the app did not build`() { + val sessionId = SessionId("@alice:example.org") + + assertThat(conversationShortcutRoomId("", sessionId)).isNull() + assertThat(conversationShortcutRoomId("@bob:example.org-!aRoom:example.org", sessionId)).isNull() + assertThat(conversationShortcutRoomId("@alice:example.org.uk-!aRoom:example.org", sessionId)).isNull() + assertThat(conversationShortcutRoomId("@alice:example.org-aRoom:example.org", sessionId)).isNull() + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt index 7b08958cd13..513c62376db 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt @@ -30,8 +30,9 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService +import io.element.android.libraries.push.api.notifications.conversations.conversationShortcutRoomId +import io.element.android.libraries.push.api.notifications.conversations.createConversationShortcutId import io.element.android.libraries.push.impl.intent.IntentProvider -import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId import io.element.android.libraries.push.impl.notifications.shortcut.filterBySession import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionObserver @@ -112,7 +113,7 @@ class DefaultNotificationConversationService( targetSize = defaultShortcutIconSize.toLong() )?.let(IconCompat::createWithBitmap) - val shortcutInfo = ShortcutInfoCompat.Builder(context, createShortcutId(sessionId, roomId)) + val shortcutInfo = ShortcutInfoCompat.Builder(context, createConversationShortcutId(sessionId, roomId)) .setShortLabel(name) .setIcon(icon) .setIntent(intentProvider.getViewRoomIntent(sessionId, roomId, threadId = null, eventId = null)) @@ -133,7 +134,7 @@ class DefaultNotificationConversationService( } override suspend fun onLeftRoom(sessionId: SessionId, roomId: RoomId) { - val shortcutsToRemove = listOf(createShortcutId(sessionId, roomId)) + val shortcutsToRemove = listOf(createConversationShortcutId(sessionId, roomId)) runCatchingExceptions { ShortcutManagerCompat.removeDynamicShortcuts(context, shortcutsToRemove) if (isRequestPinShortcutSupported) { @@ -150,16 +151,12 @@ class DefaultNotificationConversationService( override suspend fun onAvailableRoomsChanged(sessionId: SessionId, roomIds: Set) { runCatchingExceptions { - val shortcuts = ShortcutManagerCompat.getDynamicShortcuts(context) - - val shortcutsToRemove = mutableListOf() - shortcuts.filter { it.id.startsWith(sessionId.value) } - .forEach { shortcut -> - val roomId = RoomId(shortcut.id.removePrefix("$sessionId-")) - if (!roomIds.contains(roomId)) { - shortcutsToRemove.add(shortcut.id) - } + val shortcutsToRemove = ShortcutManagerCompat.getDynamicShortcuts(context) + .filter { shortcut -> + val roomId = conversationShortcutRoomId(shortcut.id, sessionId) + roomId != null && roomId !in roomIds } + .map { it.id } if (shortcutsToRemove.isNotEmpty()) { ShortcutManagerCompat.removeDynamicShortcuts(context, shortcutsToRemove) 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 26412abebcc..01a69e17b13 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 @@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getBestName import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader +import io.element.android.libraries.push.api.notifications.conversations.createConversationShortcutId import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels @@ -43,7 +44,6 @@ import io.element.android.libraries.push.impl.notifications.model.FallbackNotifi import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent -import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.appnavstate.api.ROOM_OPENED_FROM_NOTIFICATION import io.element.android.services.toolbox.api.strings.StringProvider @@ -178,7 +178,7 @@ class DefaultNotificationCreator( // https://developer.android.com/develop/ui/views/notifications/conversations .apply { if (threadId == null) { - setShortcutId(createShortcutId(roomInfo.sessionId, roomInfo.roomId)) + setShortcutId(createConversationShortcutId(roomInfo.sessionId, roomInfo.roomId)) } } .setGroupSummary(false) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt index 4a09c28f247..44c7c24f3a7 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt @@ -9,11 +9,8 @@ package io.element.android.libraries.push.impl.notifications.shortcut import androidx.core.content.pm.ShortcutInfoCompat -import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId -internal fun createShortcutId(sessionId: SessionId, roomId: RoomId) = "$sessionId-$roomId" - internal fun Iterable.filterBySession(sessionId: SessionId): Iterable { val prefix = "$sessionId-" return filter { it.id.startsWith(prefix) } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt index cb5fad7509a..f6751c2fd0a 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt @@ -23,8 +23,8 @@ 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.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder +import io.element.android.libraries.push.api.notifications.conversations.createConversationShortcutId import io.element.android.libraries.push.impl.notifications.factories.FakeIntentProvider -import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver import io.element.android.tests.testutils.robolectric.RobolectricTest @@ -79,7 +79,7 @@ class DefaultNotificationConversationServiceTest : RobolectricTest() { val context = InstrumentationRegistry.getInstrumentation().context val service = createService(context) - val shortcutId = createShortcutId(A_SESSION_ID, A_ROOM_ID) + val shortcutId = createConversationShortcutId(A_SESSION_ID, A_ROOM_ID) val shortcutInfo = ShortcutInfoCompat.Builder(context, shortcutId) .setShortLabel("Room title") .setIntent(Intent(Intent.ACTION_VIEW))