Split module deeplink to api and impl.
This commit is contained in:
parent
1682fd4c2c
commit
4e5bbaf946
25 changed files with 150 additions and 85 deletions
37
libraries/deeplink/impl/build.gradle.kts
Normal file
37
libraries/deeplink/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2022-2024 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.deeplink.impl"
|
||||
}
|
||||
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
api(projects.libraries.deeplink.api)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(libs.dagger)
|
||||
implementation(libs.androidx.corektx)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.services.toolbox.api)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 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.deeplink.impl
|
||||
|
||||
internal const val SCHEME = "elementx"
|
||||
internal const val HOST = "open"
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.deeplink.impl
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.deeplink.api.DeepLinkCreator
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultDeepLinkCreator @Inject constructor() : DeepLinkCreator {
|
||||
override fun room(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?): String {
|
||||
return buildString {
|
||||
append("$SCHEME://$HOST/")
|
||||
append(sessionId.value)
|
||||
if (roomId != null) {
|
||||
append("/")
|
||||
append(roomId.value)
|
||||
if (threadId != null) {
|
||||
append("/")
|
||||
append(threadId.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.deeplink.impl
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.deeplink.api.DeeplinkData
|
||||
import io.element.android.libraries.deeplink.api.DeeplinkParser
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultDeeplinkParser @Inject constructor() : DeeplinkParser {
|
||||
override fun getFromIntent(intent: Intent): DeeplinkData? {
|
||||
return intent
|
||||
.takeIf { it.action == Intent.ACTION_VIEW }
|
||||
?.data
|
||||
?.toDeeplinkData()
|
||||
}
|
||||
|
||||
private fun Uri.toDeeplinkData(): DeeplinkData? {
|
||||
if (scheme != SCHEME) return null
|
||||
if (host != HOST) return null
|
||||
val pathBits = path.orEmpty().split("/").drop(1)
|
||||
val sessionId = pathBits.elementAtOrNull(0)?.let(::SessionId) ?: return null
|
||||
|
||||
return when (val screenPathComponent = pathBits.elementAtOrNull(1)) {
|
||||
null -> DeeplinkData.Root(sessionId)
|
||||
else -> {
|
||||
val roomId = screenPathComponent.let(::RoomId)
|
||||
val threadId = pathBits.elementAtOrNull(2)?.let(::ThreadId)
|
||||
DeeplinkData.Room(sessionId, roomId, threadId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.deeplink.impl.usecase
|
||||
|
||||
import android.app.Activity
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.deeplink.api.usecase.InviteFriendsUseCase
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import io.element.android.libraries.androidutils.R as AndroidUtilsR
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultInviteFriendsUseCase @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val permalinkBuilder: PermalinkBuilder,
|
||||
) : InviteFriendsUseCase {
|
||||
override fun execute(activity: Activity) {
|
||||
val permalinkResult = permalinkBuilder.permalinkForUser(matrixClient.sessionId)
|
||||
permalinkResult.fold(
|
||||
onSuccess = { permalink ->
|
||||
val appName = buildMeta.applicationName
|
||||
activity.startSharePlainTextIntent(
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = stringProvider.getString(CommonStrings.action_invite_friends),
|
||||
text = stringProvider.getString(CommonStrings.invite_friends_text, appName, permalink),
|
||||
extraTitle = stringProvider.getString(CommonStrings.invite_friends_rich_title, appName),
|
||||
noActivityFoundMessage = stringProvider.getString(AndroidUtilsR.string.error_no_compatible_app_found)
|
||||
)
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 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.deeplink.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultDeepLinkCreatorTest {
|
||||
@Test
|
||||
fun room() {
|
||||
val sut = DefaultDeepLinkCreator()
|
||||
assertThat(sut.room(A_SESSION_ID, null, null))
|
||||
.isEqualTo("elementx://open/@alice:server.org")
|
||||
assertThat(sut.room(A_SESSION_ID, A_ROOM_ID, null))
|
||||
.isEqualTo("elementx://open/@alice:server.org/!aRoomId:domain")
|
||||
assertThat(sut.room(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID))
|
||||
.isEqualTo("elementx://open/@alice:server.org/!aRoomId:domain/\$aThreadId")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 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.deeplink.impl
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.core.net.toUri
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.deeplink.api.DeeplinkData
|
||||
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
|
||||
import io.element.android.tests.testutils.assertThrowsInDebug
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultDeeplinkParserTest {
|
||||
companion object {
|
||||
const val A_URI =
|
||||
"elementx://open/@alice:server.org"
|
||||
const val A_URI_WITH_ROOM =
|
||||
"elementx://open/@alice:server.org/!aRoomId:domain"
|
||||
const val A_URI_WITH_ROOM_WITH_THREAD =
|
||||
"elementx://open/@alice:server.org/!aRoomId:domain/\$aThreadId"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nominal cases`() {
|
||||
val sut = DefaultDeeplinkParser()
|
||||
assertThat(sut.getFromIntent(createIntent(A_URI)))
|
||||
.isEqualTo(DeeplinkData.Root(A_SESSION_ID))
|
||||
assertThat(sut.getFromIntent(createIntent(A_URI_WITH_ROOM)))
|
||||
.isEqualTo(DeeplinkData.Room(A_SESSION_ID, A_ROOM_ID, null))
|
||||
assertThat(sut.getFromIntent(createIntent(A_URI_WITH_ROOM_WITH_THREAD)))
|
||||
.isEqualTo(DeeplinkData.Room(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error cases`() {
|
||||
val sut = DefaultDeeplinkParser()
|
||||
// Bad scheme
|
||||
assertThat(sut.getFromIntent(createIntent("x://open/@alice:server.org"))).isNull()
|
||||
// Bad host
|
||||
assertThat(sut.getFromIntent(createIntent("elementx://close/@alice:server.org"))).isNull()
|
||||
// No session Id
|
||||
assertThat(sut.getFromIntent(createIntent("elementx://open"))).isNull()
|
||||
|
||||
assertThrowsInDebug {
|
||||
// Invalid sessionId
|
||||
sut.getFromIntent(createIntent("elementx://open/alice:server.org"))
|
||||
}
|
||||
assertThrowsInDebug {
|
||||
// Empty sessionId
|
||||
sut.getFromIntent(createIntent("elementx://open//"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIntent(uri: String): Intent {
|
||||
return Intent().apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
data = uri.toUri()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue