diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index c8c0a98dea..9b4a5e3689 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -32,7 +32,7 @@ jobs:
steps:
- name: Check membership
if: github.event.pull_request.user.login != 'renovate[bot]'
- uses: tspascoal/get-user-teams-membership@57e9f42acd78f4d0f496b3be4368fc5f62696662 # v3
+ uses: tspascoal/get-user-teams-membership@b1480b119326dde04ceffbeccd98e41892539c74 # v4.0.0
id: teams
with:
username: ${{ github.event.pull_request.user.login }}
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index 6859e78baa..ceaa86016a 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -336,7 +336,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
+ - uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
upload_reports:
name: Project Check Suite
diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml
index 0f4c8ee581..4b70cffe61 100644
--- a/.github/workflows/recordScreenshots.yml
+++ b/.github/workflows/recordScreenshots.yml
@@ -17,6 +17,7 @@ jobs:
permissions:
# Need write permissions on PRs to remove the label "Record-Screenshots"
pull-requests: write
+ contents: write
name: Record screenshots on branch ${{ github.event.pull_request.head.ref || github.ref_name }}
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'Record-Screenshots'
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 76f6344777..f393d5cdd1 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,7 @@
-
+
+
diff --git a/CHANGES.md b/CHANGES.md
index 68848d491f..df109d34a6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,45 @@
+Changes in Element X v26.04.4
+=============================
+
+
+
+## What's Changed
+### 🙌 Improvements
+* Natural media viewer swiping order by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6431
+* Replace `rustls-platform-verifier-android.aar` with single class by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6610
+* Cleanup FetchPushForegroundService by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6577
+* cleaning: Remove join button from call notify timelineItemView by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6603
+### 🐛 Bugfixes
+* Fix crash when going back to threads list by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6620
+* audio: Let EC decide alone what communication device to use by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6609
+* Fix media viewer bottom sheets not being scrollable by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6631
+### 🗣 Translations
+* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6626
+### 📄 Documentation
+* Updates to new features and some refactoring. by @mxandreas in https://github.com/element-hq/element-x-android/pull/6591
+### 🚧 In development 🚧
+* WIP : live location rendering by @ganfra in https://github.com/element-hq/element-x-android/pull/6611
+### Dependency upgrades
+* Update dependency io.element.android:element-call-embedded to v0.19.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6593
+* Update dependency androidx.annotation:annotation-jvm to v1.10.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6596
+* Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6605
+* Update dependency com.google.firebase:firebase-bom to v34.12.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6604
+* Update actions/upload-artifact action to v7.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6614
+* Update plugin dependencycheck to v12.2.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6621
+* Update actions/github-script action to v9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6606
+* Update peter-evans/create-pull-request action to v8.1.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6615
+* Update dependencyAnalysis to v3.7.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6616
+* Update dependency org.matrix.rustcomponents:sdk-android to v26.04.21 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6635
+### Others
+* Settings UI update. by @bmarty in https://github.com/element-hq/element-x-android/pull/6602
+* Support replying to messages with voice recordings by @kalix127 in https://github.com/element-hq/element-x-android/pull/6464
+* Add Black theme option for battery saving on OLED displays by @timurgilfanov in https://github.com/element-hq/element-x-android/pull/6441
+* Fix | When selecting earpiece twice in a row the proximity sensor get wrongly disabled by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6627
+* Update wording of deactivate account screen by @bmarty in https://github.com/element-hq/element-x-android/pull/6633
+
+
+**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.04.3...v26.04.4
+
Changes in Element X v26.04.3
=============================
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a4ee1c8459..da90ec82e4 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -103,13 +103,13 @@ android {
logger.warnInBox("Building ${defaultConfig.applicationId} ($baseAppName) [$buildType]")
buildTypes {
- val oidcRedirectSchemeBase = BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element.android"
+ val oAuthRedirectSchemeBase = BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element.android"
getByName("debug") {
resValue("string", "app_name", "$baseAppName dbg")
resValue(
"string",
"login_redirect_scheme",
- "$oidcRedirectSchemeBase.debug",
+ "$oAuthRedirectSchemeBase.debug",
)
applicationIdSuffix = ".debug"
signingConfig = signingConfigs.getByName("debug")
@@ -120,7 +120,7 @@ android {
resValue(
"string",
"login_redirect_scheme",
- oidcRedirectSchemeBase,
+ oAuthRedirectSchemeBase,
)
signingConfig = signingConfigs.getByName("debug")
@@ -157,7 +157,7 @@ android {
resValue(
"string",
"login_redirect_scheme",
- "$oidcRedirectSchemeBase.nightly",
+ "$oAuthRedirectSchemeBase.nightly",
)
matchingFallbacks += listOf("release")
signingConfig = signingConfigs.getByName("nightly")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6041fbb118..d63e18ec1a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -75,7 +75,7 @@
android:scheme="elementx" />
diff --git a/app/src/main/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProvider.kt b/app/src/main/kotlin/io/element/android/x/oidc/DefaultOAuthRedirectUrlProvider.kt
similarity index 82%
rename from app/src/main/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProvider.kt
rename to app/src/main/kotlin/io/element/android/x/oidc/DefaultOAuthRedirectUrlProvider.kt
index ad4f9a47b2..16db564aaf 100644
--- a/app/src/main/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProvider.kt
+++ b/app/src/main/kotlin/io/element/android/x/oidc/DefaultOAuthRedirectUrlProvider.kt
@@ -10,14 +10,14 @@ package io.element.android.x.oidc
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
-import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
+import io.element.android.libraries.matrix.api.auth.OAuthRedirectUrlProvider
import io.element.android.services.toolbox.api.strings.StringProvider
import io.element.android.x.R
@ContributesBinding(AppScope::class)
-class DefaultOidcRedirectUrlProvider(
+class DefaultOAuthRedirectUrlProvider(
private val stringProvider: StringProvider,
-) : OidcRedirectUrlProvider {
+) : OAuthRedirectUrlProvider {
override fun provide() = buildString {
append(stringProvider.getString(R.string.login_redirect_scheme))
append(":/")
diff --git a/app/src/test/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProviderTest.kt b/app/src/test/kotlin/io/element/android/x/oidc/DefaultOAuthRedirectUrlProviderTest.kt
similarity index 89%
rename from app/src/test/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProviderTest.kt
rename to app/src/test/kotlin/io/element/android/x/oidc/DefaultOAuthRedirectUrlProviderTest.kt
index 18567355d2..c26e3dc692 100644
--- a/app/src/test/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProviderTest.kt
+++ b/app/src/test/kotlin/io/element/android/x/oidc/DefaultOAuthRedirectUrlProviderTest.kt
@@ -13,13 +13,13 @@ import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.x.R
import org.junit.Test
-class DefaultOidcRedirectUrlProviderTest {
+class DefaultOAuthRedirectUrlProviderTest {
@Test
fun `test provide`() {
val stringProvider = FakeStringProvider(
defaultResult = "str"
)
- val sut = DefaultOidcRedirectUrlProvider(
+ val sut = DefaultOAuthRedirectUrlProvider(
stringProvider = stringProvider,
)
val result = sut.provide()
diff --git a/appconfig/build.gradle.kts b/appconfig/build.gradle.kts
index 45496acb77..64b9b76a14 100644
--- a/appconfig/build.gradle.kts
+++ b/appconfig/build.gradle.kts
@@ -48,6 +48,8 @@ android {
}
dependencies {
+ implementation(libs.coroutines.core)
implementation(libs.androidx.annotationjvm)
+ implementation(libs.androidx.corektx)
implementation(projects.libraries.matrix.api)
}
diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/ProtectionConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/ProtectionConfig.kt
new file mode 100644
index 0000000000..f6ad71eeb1
--- /dev/null
+++ b/appconfig/src/main/kotlin/io/element/android/appconfig/ProtectionConfig.kt
@@ -0,0 +1,15 @@
+/*
+ * 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.appconfig
+
+object ProtectionConfig {
+ /**
+ * The maximum length of a room name, to limit attack vectors in room invite.
+ */
+ const val MAX_ROOM_NAME_LENGTH = 128
+}
diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts
index 24a0355b3f..7440ecd2bf 100644
--- a/appnav/build.gradle.kts
+++ b/appnav/build.gradle.kts
@@ -33,13 +33,14 @@ dependencies {
implementation(projects.libraries.deeplink.api)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.matrix.api)
- implementation(projects.libraries.oidc.api)
+ implementation(projects.libraries.oauth.api)
implementation(projects.libraries.preferences.api)
implementation(projects.libraries.push.api)
implementation(projects.libraries.pushproviders.api)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.matrixmedia.api)
+ implementation(projects.libraries.sessionStorage.api)
implementation(projects.libraries.uiCommon)
implementation(projects.libraries.uiStrings)
implementation(projects.features.login.api)
@@ -59,7 +60,7 @@ dependencies {
testImplementation(projects.features.login.test)
testImplementation(projects.features.share.test)
testImplementation(projects.libraries.matrix.test)
- testImplementation(projects.libraries.oidc.test)
+ testImplementation(projects.libraries.oauth.test)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.push.test)
testImplementation(projects.libraries.pushproviders.test)
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 0e458d3b9c..acf7b66db9 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
@@ -63,8 +63,8 @@ import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.asEventId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.permalink.PermalinkData
-import io.element.android.libraries.oidc.api.OidcAction
-import io.element.android.libraries.oidc.api.OidcActionFlow
+import io.element.android.libraries.oauth.api.OAuthAction
+import io.element.android.libraries.oauth.api.OAuthActionFlow
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
@@ -95,7 +95,7 @@ class RootFlowNode(
private val signedOutEntryPoint: SignedOutEntryPoint,
private val accountSelectEntryPoint: AccountSelectEntryPoint,
private val intentResolver: IntentResolver,
- private val oidcActionFlow: OidcActionFlow,
+ private val oAuthActionFlow: OAuthActionFlow,
private val featureFlagService: FeatureFlagService,
private val announcementService: AnnouncementService,
private val analyticsService: AnalyticsService,
@@ -392,7 +392,7 @@ class RootFlowNode(
navigateTo(resolvedIntent.deeplinkData)
}
is ResolvedIntent.Login -> onLoginLink(resolvedIntent.params)
- is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction)
+ is ResolvedIntent.OAuth -> onOAuthAction(resolvedIntent.oAuthAction)
is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData)
is ResolvedIntent.IncomingShare -> onIncomingShare(resolvedIntent.shareIntentData)
}
@@ -529,8 +529,8 @@ class RootFlowNode(
}
}
- private fun onOidcAction(oidcAction: OidcAction) {
- oidcActionFlow.post(oidcAction)
+ private fun onOAuthAction(oAuthAction: OAuthAction) {
+ oAuthActionFlow.post(oAuthAction)
}
private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode {
diff --git a/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt b/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt
index 6844db3ed6..ee316f00aa 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt
@@ -18,13 +18,13 @@ import io.element.android.libraries.deeplink.api.DeeplinkData
import io.element.android.libraries.deeplink.api.DeeplinkParser
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
-import io.element.android.libraries.oidc.api.OidcAction
-import io.element.android.libraries.oidc.api.OidcIntentResolver
+import io.element.android.libraries.oauth.api.OAuthAction
+import io.element.android.libraries.oauth.api.OAuthIntentResolver
import timber.log.Timber
sealed interface ResolvedIntent {
data class Navigation(val deeplinkData: DeeplinkData) : ResolvedIntent
- data class Oidc(val oidcAction: OidcAction) : ResolvedIntent
+ data class OAuth(val oAuthAction: OAuthAction) : ResolvedIntent
data class Permalink(val permalinkData: PermalinkData) : ResolvedIntent
data class Login(val params: LoginParams) : ResolvedIntent
data class IncomingShare(val shareIntentData: ShareIntentData) : ResolvedIntent
@@ -34,7 +34,7 @@ sealed interface ResolvedIntent {
class IntentResolver(
private val deeplinkParser: DeeplinkParser,
private val loginIntentResolver: LoginIntentResolver,
- private val oidcIntentResolver: OidcIntentResolver,
+ private val oAuthIntentResolver: OAuthIntentResolver,
private val permalinkParser: PermalinkParser,
private val shareIntentHandler: ShareIntentHandler,
) {
@@ -45,9 +45,9 @@ class IntentResolver(
val deepLinkData = deeplinkParser.getFromIntent(intent)
if (deepLinkData != null) return ResolvedIntent.Navigation(deepLinkData)
- // Coming during login using Oidc?
- val oidcAction = oidcIntentResolver.resolve(intent)
- if (oidcAction != null) return ResolvedIntent.Oidc(oidcAction)
+ // Coming during login using OAuth?
+ val oAuthAction = oAuthIntentResolver.resolve(intent)
+ if (oAuthAction != null) return ResolvedIntent.OAuth(oAuthAction)
val actionViewData = intent
.takeIf { it.action == Intent.ACTION_VIEW }
diff --git a/appnav/src/main/res/values-zh/translations.xml b/appnav/src/main/res/values-zh/translations.xml
index 406471196e..f6eac30310 100644
--- a/appnav/src/main/res/values-zh/translations.xml
+++ b/appnav/src/main/res/values-zh/translations.xml
@@ -1,6 +1,6 @@
- "登出并升级"
+ "注销并升级"
"%1$s 不再支持旧协议。请注销并重新登录以继续使用该应用程序。"
- "您的服务器不再支持旧协议。请登出并重新登录以继续使用此应用。"
+ "你的主服务器不再支持旧协议。请注销并重新登录以继续使用此 app。"
diff --git a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt
index 576e1aaea6..451ca279f8 100644
--- a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt
+++ b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt
@@ -26,8 +26,8 @@ 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.libraries.matrix.test.permalink.FakePermalinkParser
-import io.element.android.libraries.oidc.api.OidcAction
-import io.element.android.libraries.oidc.test.FakeOidcIntentResolver
+import io.element.android.libraries.oauth.api.OAuthAction
+import io.element.android.libraries.oauth.test.FakeOAuthIntentResolver
import io.element.android.tests.testutils.lambda.lambdaError
import org.junit.Test
import org.junit.runner.RunWith
@@ -170,9 +170,9 @@ class IntentResolverTest {
}
@Test
- fun `test resolve oidc`() {
+ fun `test resolve OAuth`() {
val sut = createIntentResolver(
- oidcIntentResolverResult = { OidcAction.GoBack() },
+ oAuthIntentResolverResult = { OAuthAction.GoBack() },
)
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
action = Intent.ACTION_VIEW
@@ -180,8 +180,8 @@ class IntentResolverTest {
}
val result = sut.resolve(intent)
assertThat(result).isEqualTo(
- ResolvedIntent.Oidc(
- oidcAction = OidcAction.GoBack()
+ ResolvedIntent.OAuth(
+ oAuthAction = OAuthAction.GoBack()
)
)
}
@@ -194,7 +194,7 @@ class IntentResolverTest {
val sut = createIntentResolver(
loginIntentResolverResult = { null },
permalinkParserResult = { permalinkData },
- oidcIntentResolverResult = { null },
+ oAuthIntentResolverResult = { null },
)
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
action = Intent.ACTION_VIEW
@@ -213,7 +213,7 @@ class IntentResolverTest {
val sut = createIntentResolver(
permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) },
loginIntentResolverResult = { null },
- oidcIntentResolverResult = { null },
+ oAuthIntentResolverResult = { null },
)
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
action = Intent.ACTION_VIEW
@@ -230,7 +230,7 @@ class IntentResolverTest {
)
val sut = createIntentResolver(
permalinkParserResult = { permalinkData },
- oidcIntentResolverResult = { null },
+ oAuthIntentResolverResult = { null },
)
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
action = Intent.ACTION_BATTERY_LOW
@@ -244,7 +244,7 @@ class IntentResolverTest {
fun `test incoming share simple`() {
val shareIntentData = ShareIntentData.PlainText("Hello")
val sut = createIntentResolver(
- oidcIntentResolverResult = { null },
+ oAuthIntentResolverResult = { null },
onIncomingShareIntent = { shareIntentData },
)
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
@@ -260,7 +260,7 @@ class IntentResolverTest {
val fileUri = "content://com.example.app/file1.jpg".toUri()
val shareIntentData = ShareIntentData.Uris(text = "Hello", uris = listOf(UriToShare(fileUri, "image/jpg")))
val sut = createIntentResolver(
- oidcIntentResolverResult = { null },
+ oAuthIntentResolverResult = { null },
onIncomingShareIntent = { shareIntentData },
)
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
@@ -277,7 +277,7 @@ class IntentResolverTest {
val sut = createIntentResolver(
permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) },
loginIntentResolverResult = { null },
- oidcIntentResolverResult = { null },
+ oAuthIntentResolverResult = { null },
)
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
action = Intent.ACTION_VIEW
@@ -292,7 +292,7 @@ class IntentResolverTest {
val aLoginParams = LoginParams("accountProvider", null)
val sut = createIntentResolver(
loginIntentResolverResult = { aLoginParams },
- oidcIntentResolverResult = { null },
+ oAuthIntentResolverResult = { null },
)
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
action = Intent.ACTION_VIEW
@@ -306,7 +306,7 @@ class IntentResolverTest {
deeplinkParserResult: DeeplinkData? = null,
permalinkParserResult: (String) -> PermalinkData = { lambdaError() },
loginIntentResolverResult: (String) -> LoginParams? = { lambdaError() },
- oidcIntentResolverResult: (Intent) -> OidcAction? = { lambdaError() },
+ oAuthIntentResolverResult: (Intent) -> OAuthAction? = { lambdaError() },
onIncomingShareIntent: (Intent) -> ShareIntentData? = { null },
): IntentResolver {
return IntentResolver(
@@ -314,8 +314,8 @@ class IntentResolverTest {
loginIntentResolver = FakeLoginIntentResolver(
parseResult = loginIntentResolverResult,
),
- oidcIntentResolver = FakeOidcIntentResolver(
- resolveResult = oidcIntentResolverResult,
+ oAuthIntentResolver = FakeOAuthIntentResolver(
+ resolveResult = oAuthIntentResolverResult,
),
permalinkParser = FakePermalinkParser(
result = permalinkParserResult
diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt
index 902f446a6f..18c8cfd7b9 100644
--- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt
+++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt
@@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState
-import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
+import io.element.android.libraries.matrix.api.oauth.AccountManagementAction
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncState
diff --git a/build.gradle.kts b/build.gradle.kts
index 92847f39b7..474b868eda 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -46,7 +46,7 @@ allprojects {
config.from(files("$rootDir/tools/detekt/detekt.yml"))
}
dependencies {
- detektPlugins("io.nlopez.compose.rules:detekt:0.5.6")
+ detektPlugins("io.nlopez.compose.rules:detekt:0.5.8")
detektPlugins(project(":tests:detekt-rules"))
}
diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md
index a264bfec63..74020ead98 100644
--- a/docs/_developer_onboarding.md
+++ b/docs/_developer_onboarding.md
@@ -144,6 +144,11 @@ Prerequisites:
export ANDROID_HOME=$HOME/android/sdk
```
+* On macos ensure gnu-getopt is installed
+ ```
+ brew install gnu-getopt
+ ```
+
You can then build the Rust SDK by running the script
[`tools/sdk/build-rust-sdk`](../tools/sdk/build-rust-sdk). Type
`./tools/sdk/build-rust-sdk --help` for help.
diff --git a/docs/oidc.md b/docs/oauth.md
similarity index 81%
rename from docs/oidc.md
rename to docs/oauth.md
index 23709b608c..1080c64b0e 100644
--- a/docs/oidc.md
+++ b/docs/oauth.md
@@ -1,4 +1,4 @@
-This file contains some rough notes about Oidc implementation, with some examples of actual data.
+This file contains some rough notes about OAuth implementation, with some examples of actual data.
[ios implementation](https://github.com/element-hq/element-x-ios/compare/develop...doug/oidc-temp)
@@ -25,7 +25,7 @@ tosUri = "https://element.io/user-terms-of-service",
policyUri = "https://element.io/privacy"
-Example of OidcData (from presentUrl callback):
+Example of OAuthData (from presentUrl callback):
url: https://auth-oidc.lab.element.dev/authorize?response_type=code&client_id=01GYCAGG3PA70CJ97ZVP0WFJY3&redirect_uri=io.element%3A%2Fcallback&scope=openid+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Aapi%3A*+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Adevice%3AYAgcPW4mcG&state=ex6mNJVFZ5jn9wL8&nonce=NZ93DOyIGQd9exPQ&code_challenge_method=S256&code_challenge=FFRcPALNSPCh-ZgpyTRFu_h8NZJVncfvihbfT9CyX8U&prompt=consent
Formatted url:
@@ -43,8 +43,8 @@ https://auth-oidc.lab.element.dev/authorize?
state: ex6mNJVFZ5jn9wL8
-Oidc client example: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/examples/oidc_cli/src/main.rs
-Oidc sdk doc: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/crates/matrix-sdk/src/oidc.rs
+OAuth client example: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/examples/oidc_cli/src/main.rs
+OAuth sdk doc: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/crates/matrix-sdk/src/oidc.rs
Test server:
diff --git a/enterprise b/enterprise
index cdde60c158..fb7e9287d9 160000
--- a/enterprise
+++ b/enterprise
@@ -1 +1 @@
-Subproject commit cdde60c158ecd0987a3ba6fd79a4617551aff463
+Subproject commit fb7e9287d9d446012925139842d9aaa8e99a74dc
diff --git a/fastlane/metadata/android/en-US/changelogs/202605000.txt b/fastlane/metadata/android/en-US/changelogs/202605000.txt
new file mode 100644
index 0000000000..a4b397f1bb
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/202605000.txt
@@ -0,0 +1,2 @@
+Main changes in this version: bug fixes and improvements.
+Full changelog: https://github.com/element-hq/element-x-android/releases
\ No newline at end of file
diff --git a/features/analytics/api/src/main/res/values-ja/translations.xml b/features/analytics/api/src/main/res/values-ja/translations.xml
index 5554ee162f..e1495271d3 100644
--- a/features/analytics/api/src/main/res/values-ja/translations.xml
+++ b/features/analytics/api/src/main/res/values-ja/translations.xml
@@ -1,7 +1,7 @@
- "問題発見のため、匿名の使用データの共有にご協力ください。"
- "利用規約の全文を%1$sから確認することができます。"
+ "改善のため、匿名の使用データの共有にご協力ください。"
+ "規約の全文は%1$sから確認することができます。"
"こちら"
"使用データを共有"
diff --git a/features/analytics/api/src/main/res/values-zh/translations.xml b/features/analytics/api/src/main/res/values-zh/translations.xml
index e5f9fccd66..8f1c1699d9 100644
--- a/features/analytics/api/src/main/res/values-zh/translations.xml
+++ b/features/analytics/api/src/main/res/values-zh/translations.xml
@@ -1,7 +1,7 @@
"共享匿名使用数据以帮助我们排查问题。"
- "您可以阅读我们的所有条款 %1$s。"
+ "你可以点击 %1$s 阅读我们的所有条款。"
"此处"
"共享分析数据"
diff --git a/features/analytics/impl/src/main/res/values-ja/translations.xml b/features/analytics/impl/src/main/res/values-ja/translations.xml
index 2cee69962c..162e01ecb0 100644
--- a/features/analytics/impl/src/main/res/values-ja/translations.xml
+++ b/features/analytics/impl/src/main/res/values-ja/translations.xml
@@ -1,8 +1,8 @@
"いかなる個人情報も記録, 分析されることはありません"
- "問題発見のため、匿名の使用データの共有にご協力ください。"
- "利用規約の全文を%1$sから確認することができます。"
+ "改善のため、匿名の使用データの共有にご協力ください。"
+ "規約の全文は%1$sから確認することができます。"
"こちら"
"いつでも設定は変更できます"
"情報が第三者に共有されることはありません"
diff --git a/features/analytics/impl/src/main/res/values-zh/translations.xml b/features/analytics/impl/src/main/res/values-zh/translations.xml
index d18650654b..678d506287 100644
--- a/features/analytics/impl/src/main/res/values-zh/translations.xml
+++ b/features/analytics/impl/src/main/res/values-zh/translations.xml
@@ -2,9 +2,9 @@
"我们不会记录或分析任何个人数据"
"共享匿名使用数据以帮助我们排查问题。"
- "您可以阅读我们的所有条款 %1$s。"
+ "你可以点击 %1$s 阅读我们的所有条款。"
"此处"
"可以随时关闭此功能"
- "我们不会与第三方共享您的数据"
+ "我们不会与第三方共享你的数据"
"帮助改进 %1$s"
diff --git a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementViewTest.kt b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementViewTest.kt
index b69037e61a..b7932898a8 100644
--- a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementViewTest.kt
+++ b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.announcement.impl.fullscreen
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.announcement.api.Announcement
import io.element.android.features.announcement.impl.AnnouncementEvent
@@ -20,43 +23,39 @@ import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class FullscreenAnnouncementViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back sends a AnnouncementEvent`() {
+ fun `clicking on back sends a AnnouncementEvent`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setFullscreenAnnouncementView(
+ setFullscreenAnnouncementView(
anAnnouncementState(
announcement = Announcement.Fullscreen.Space,
eventSink = eventsRecorder,
),
)
- rule.pressBackKey()
+ pressBackKey()
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
}
@Test
- fun `clicking on Continue sends a AnnouncementEvent`() {
+ fun `clicking on Continue sends a AnnouncementEvent`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setFullscreenAnnouncementView(
+ setFullscreenAnnouncementView(
anAnnouncementState(
announcement = Announcement.Fullscreen.Space,
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
}
}
-private fun AndroidComposeTestRule.setFullscreenAnnouncementView(
+private fun AndroidComposeUiTest.setFullscreenAnnouncementView(
state: AnnouncementState,
) {
setContent {
diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallData.kt
similarity index 50%
rename from features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt
rename to features/call/api/src/main/kotlin/io/element/android/features/call/api/CallData.kt
index 4b09813418..c1dcf573c6 100644
--- a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt
+++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallData.kt
@@ -14,22 +14,9 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import kotlinx.parcelize.Parcelize
-sealed interface CallType : NodeInputs, Parcelable {
- @Parcelize
- data class ExternalUrl(val url: String) : CallType {
- override fun toString(): String {
- return "ExternalUrl"
- }
- }
-
- @Parcelize
- data class RoomCall(
- val sessionId: SessionId,
- val roomId: RoomId,
- val isAudioCall: Boolean
- ) : CallType {
- override fun toString(): String {
- return "RoomCall(sessionId=$sessionId, roomId=$roomId, isAudioCall=$isAudioCall)"
- }
- }
-}
+@Parcelize
+data class CallData(
+ val sessionId: SessionId,
+ val roomId: RoomId,
+ val isAudioCall: Boolean
+) : NodeInputs, Parcelable
diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt
index caa557f4de..2976635ee2 100644
--- a/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt
+++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt
@@ -17,13 +17,13 @@ import io.element.android.libraries.matrix.api.core.UserId
interface ElementCallEntryPoint {
/**
* Start a call of the given type.
- * @param callType The type of call to start.
+ * @param callData The data of call to start.
*/
- fun startCall(callType: CallType)
+ fun startCall(callData: CallData)
/**
* Handle an incoming call.
- * @param callType The type of call.
+ * @param callData The data of call.
* @param eventId The event id of the event that started the call.
* @param senderId The user id of the sender of the event that started the call.
* @param roomName The name of the room the call is in.
@@ -35,7 +35,7 @@ interface ElementCallEntryPoint {
* @param textContent The text content of the notification. If null the default content from the system will be used.
*/
suspend fun handleIncomingCall(
- callType: CallType.RoomCall,
+ callData: CallData,
eventId: EventId,
senderId: UserId,
roomName: String?,
diff --git a/features/call/impl/src/main/AndroidManifest.xml b/features/call/impl/src/main/AndroidManifest.xml
index daf1a910c9..c35c6843ff 100644
--- a/features/call/impl/src/main/AndroidManifest.xml
+++ b/features/call/impl/src/main/AndroidManifest.xml
@@ -30,44 +30,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:taskAffinity="io.element.android.features.call" />
(null) }
- fun handleEvent(event: PictureInPictureEvents) {
+ fun handleEvent(event: PictureInPictureEvent) {
when (event) {
- is PictureInPictureEvents.SetPipController -> {
+ is PictureInPictureEvent.SetPipController -> {
pipController = event.pipController
}
- PictureInPictureEvents.EnterPictureInPicture -> {
+ PictureInPictureEvent.EnterPictureInPicture -> {
coroutineScope.launch {
switchToPip(pipController)
}
}
- is PictureInPictureEvents.OnPictureInPictureModeChanged -> {
+ is PictureInPictureEvent.OnPictureInPictureModeChanged -> {
Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: ${event.isInPip}")
isInPictureInPicture = event.isInPip
if (event.isInPip) {
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt
index b1fef4f28b..108589edb9 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt
@@ -11,5 +11,5 @@ package io.element.android.features.call.impl.pip
data class PictureInPictureState(
val supportPip: Boolean,
val isInPictureInPicture: Boolean,
- val eventSink: (PictureInPictureEvents) -> Unit,
+ val eventSink: (PictureInPictureEvent) -> Unit,
)
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt
index 6324820eec..f4a78294b6 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt
@@ -11,7 +11,7 @@ package io.element.android.features.call.impl.pip
fun aPictureInPictureState(
supportPip: Boolean = false,
isInPictureInPicture: Boolean = false,
- eventSink: (PictureInPictureEvents) -> Unit = {},
+ eventSink: (PictureInPictureEvent) -> Unit = {},
): PictureInPictureState {
return PictureInPictureState(
supportPip = supportPip,
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt
index 179e6c2b22..bf27e8d39d 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt
@@ -13,7 +13,7 @@ import android.content.Context
import android.content.Intent
import androidx.core.content.IntentCompat
import dev.zacsweers.metro.Inject
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.impl.di.CallBindings
import io.element.android.features.call.impl.notifications.CallNotificationData
import io.element.android.features.call.impl.utils.ActiveCallManager
@@ -42,7 +42,7 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() {
context.bindings().inject(this)
appCoroutineScope.launch {
activeCallManager.hangUpCall(
- callType = CallType.RoomCall(
+ callData = CallData(
sessionId = notificationData.sessionId,
roomId = notificationData.roomId,
isAudioCall = notificationData.audioOnly
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallNotificationDataProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallNotificationDataProvider.kt
index 3a51a014df..9e551b3e1b 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallNotificationDataProvider.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallNotificationDataProvider.kt
@@ -9,6 +9,8 @@ package io.element.android.features.call.impl.ui
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.call.impl.notifications.CallNotificationData
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
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
@@ -34,8 +36,8 @@ internal fun aCallNotificationData(
roomId = RoomId("!1234:matrix.org"),
eventId = EventId("\$asdadadsad:matrix.org"),
senderId = UserId("@bob:matrix.org"),
- roomName = "A room",
- senderName = "Bob",
+ roomName = ROOM_NAME,
+ senderName = USER_NAME_BOB,
avatarUrl = null,
notificationChannelId = "incoming_call",
timestamp = 0L,
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvents.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvent.kt
similarity index 78%
rename from features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvents.kt
rename to features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvent.kt
index 8fbbce896f..357559c3f9 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvents.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvent.kt
@@ -10,8 +10,8 @@ package io.element.android.features.call.impl.ui
import io.element.android.features.call.impl.utils.WidgetMessageInterceptor
-sealed interface CallScreenEvents {
- data object Hangup : CallScreenEvents
- data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvents
- data class OnWebViewError(val description: String?) : CallScreenEvents
+sealed interface CallScreenEvent {
+ data object Hangup : CallScreenEvent
+ data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvent
+ data class OnWebViewError(val description: String?) : CallScreenEvent
}
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt
index da2c57c0ac..7d8e20967f 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt
@@ -23,7 +23,7 @@ import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.compound.theme.ElementTheme
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.impl.data.WidgetMessage
import io.element.android.features.call.impl.utils.ActiveCallManager
import io.element.android.features.call.impl.utils.CallWidgetProvider
@@ -52,7 +52,7 @@ import kotlin.time.Duration.Companion.seconds
@AssistedInject
class CallScreenPresenter(
- @Assisted private val callType: CallType,
+ @Assisted private val callData: CallData,
@Assisted private val navigator: CallScreenNavigator,
private val callWidgetProvider: CallWidgetProvider,
userAgentProvider: UserAgentProvider,
@@ -69,10 +69,9 @@ class CallScreenPresenter(
) : Presenter {
@AssistedFactory
interface Factory {
- fun create(callType: CallType, navigator: CallScreenNavigator): CallScreenPresenter
+ fun create(callData: CallData, navigator: CallScreenNavigator): CallScreenPresenter
}
- private val isInWidgetMode = callType is CallType.RoomCall
private val userAgent = userAgentProvider.provide()
@Composable
@@ -90,9 +89,9 @@ class CallScreenPresenter(
DisposableEffect(Unit) {
coroutineScope.launch {
// Sets the call as joined
- activeCallManager.joinedCall(callType)
+ activeCallManager.joinedCall(callData)
fetchRoomCallUrl(
- inputs = callType,
+ callData = callData,
urlState = urlState,
callWidgetDriver = callWidgetDriver,
languageTag = languageTag,
@@ -100,19 +99,10 @@ class CallScreenPresenter(
)
}
onDispose {
- appCoroutineScope.launch { activeCallManager.hangUpCall(callType) }
+ appCoroutineScope.launch { activeCallManager.hangUpCall(callData) }
}
}
-
- when (callType) {
- is CallType.ExternalUrl -> {
- // No analytics yet for external calls
- }
- is CallType.RoomCall -> {
- screenTracker.TrackScreen(screen = MobileScreen.ScreenName.RoomCall)
- }
- }
-
+ screenTracker.TrackScreen(screen = MobileScreen.ScreenName.RoomCall)
HandleMatrixClientSyncState()
callWidgetDriver.value?.let { driver ->
@@ -149,25 +139,22 @@ class CallScreenPresenter(
.launchIn(this)
}
- if (callType is CallType.RoomCall) {
- // Note: For external calls isWidgetLoaded will always be false
- LaunchedEffect(Unit) {
- // Wait for the call to be joined, if it takes too long, we display an error
- delay(10.seconds)
+ LaunchedEffect(Unit) {
+ // Wait for the call to be joined, if it takes too long, we display an error
+ delay(10.seconds)
- if (!isWidgetLoaded) {
- Timber.w("The call took too long to load. Displaying an error before exiting.")
+ if (!isWidgetLoaded) {
+ Timber.w("The call took too long to load. Displaying an error before exiting.")
- // This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call
- webViewError = ""
- }
+ // This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call
+ webViewError = ""
}
}
}
- fun handleEvent(event: CallScreenEvents) {
+ fun handleEvent(event: CallScreenEvent) {
when (event) {
- is CallScreenEvents.Hangup -> {
+ is CallScreenEvent.Hangup -> {
val widgetId = callWidgetDriver.value?.id
val interceptor = messageInterceptor.value
if (widgetId != null && interceptor != null && isWidgetLoaded) {
@@ -187,10 +174,10 @@ class CallScreenPresenter(
}
}
}
- is CallScreenEvents.SetupMessageChannels -> {
+ is CallScreenEvent.SetupMessageChannels -> {
messageInterceptor.value = event.widgetMessageInterceptor
}
- is CallScreenEvents.OnWebViewError -> {
+ is CallScreenEvent.OnWebViewError -> {
if (!ignoreWebViewError) {
webViewError = event.description.orEmpty()
}
@@ -204,37 +191,29 @@ class CallScreenPresenter(
webViewError = webViewError,
userAgent = userAgent,
isCallActive = isWidgetLoaded,
- isInWidgetMode = isInWidgetMode,
eventSink = ::handleEvent,
)
}
private suspend fun fetchRoomCallUrl(
- inputs: CallType,
+ callData: CallData,
urlState: MutableState>,
callWidgetDriver: MutableState,
languageTag: String?,
theme: String?,
) {
urlState.runCatchingUpdatingState {
- when (inputs) {
- is CallType.ExternalUrl -> {
- inputs.url
- }
- is CallType.RoomCall -> {
- val result = callWidgetProvider.getWidget(
- sessionId = inputs.sessionId,
- roomId = inputs.roomId,
- clientId = UUID.randomUUID().toString(),
- isAudioCall = inputs.isAudioCall,
- languageTag = languageTag,
- theme = theme,
- ).getOrThrow()
- callWidgetDriver.value = result.driver
- Timber.d("Call widget driver initialized for sessionId: ${inputs.sessionId}, roomId: ${inputs.roomId}")
- result.url
- }
- }
+ val result = callWidgetProvider.getWidget(
+ sessionId = callData.sessionId,
+ roomId = callData.roomId,
+ clientId = UUID.randomUUID().toString(),
+ isAudioCall = callData.isAudioCall,
+ languageTag = languageTag,
+ theme = theme,
+ ).getOrThrow()
+ callWidgetDriver.value = result.driver
+ Timber.d("Call widget driver initialized for sessionId: ${callData.sessionId}, roomId: ${callData.roomId}")
+ result.url
}
}
@@ -242,12 +221,11 @@ class CallScreenPresenter(
private fun HandleMatrixClientSyncState() {
val coroutineScope = rememberCoroutineScope()
DisposableEffect(Unit) {
- val roomCallType = callType as? CallType.RoomCall ?: return@DisposableEffect onDispose {}
- val client = matrixClientsProvider.getOrNull(roomCallType.sessionId) ?: return@DisposableEffect onDispose {
- Timber.w("No MatrixClient found for sessionId, can't send call notification: ${roomCallType.sessionId}")
+ val client = matrixClientsProvider.getOrNull(callData.sessionId) ?: return@DisposableEffect onDispose {
+ Timber.w("No MatrixClient found for sessionId, can't send call notification: ${callData.sessionId}")
}
coroutineScope.launch {
- Timber.d("Observing sync state in-call for sessionId: ${roomCallType.sessionId}")
+ Timber.d("Observing sync state in-call for sessionId: ${callData.sessionId}")
client.syncService.syncState
.collect { state ->
if (state != SyncState.Running) {
@@ -256,7 +234,7 @@ class CallScreenPresenter(
}
}
onDispose {
- Timber.d("Stopped observing sync state in-call for sessionId: ${roomCallType.sessionId}")
+ Timber.d("Stopped observing sync state in-call for sessionId: ${callData.sessionId}")
// Make sure we mark the call as ended in the app state
appForegroundStateService.updateIsInCallState(false)
}
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt
index c07594aebb..86b4cc439f 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt
@@ -15,6 +15,5 @@ data class CallScreenState(
val webViewError: String?,
val userAgent: String,
val isCallActive: Boolean,
- val isInWidgetMode: Boolean,
- val eventSink: (CallScreenEvents) -> Unit,
+ val eventSink: (CallScreenEvent) -> Unit,
)
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt
index 3e72f96f87..155c5d3380 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt
@@ -26,15 +26,13 @@ internal fun aCallScreenState(
webViewError: String? = null,
userAgent: String = "",
isCallActive: Boolean = true,
- isInWidgetMode: Boolean = false,
- eventSink: (CallScreenEvents) -> Unit = {},
+ eventSink: (CallScreenEvent) -> Unit = {},
): CallScreenState {
return CallScreenState(
urlState = urlState,
webViewError = webViewError,
userAgent = userAgent,
isCallActive = isCallActive,
- isInWidgetMode = isInWidgetMode,
eventSink = eventSink,
)
}
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt
index f8657a9ece..1c68a62f55 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt
@@ -33,7 +33,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.viewinterop.AndroidView
import io.element.android.features.call.impl.R
-import io.element.android.features.call.impl.pip.PictureInPictureEvents
+import io.element.android.features.call.impl.pip.PictureInPictureEvent
import io.element.android.features.call.impl.pip.PictureInPictureState
import io.element.android.features.call.impl.pip.aPictureInPictureState
import io.element.android.features.call.impl.utils.InvalidAudioDeviceReason
@@ -66,9 +66,9 @@ internal fun CallScreenView(
) {
fun handleBack() {
if (pipState.supportPip) {
- pipState.eventSink.invoke(PictureInPictureEvents.EnterPictureInPicture)
+ pipState.eventSink.invoke(PictureInPictureEvent.EnterPictureInPicture)
} else {
- state.eventSink(CallScreenEvents.Hangup)
+ state.eventSink(CallScreenEvent.Hangup)
}
}
@@ -84,7 +84,7 @@ internal fun CallScreenView(
append(stringResource(CommonStrings.error_unknown))
state.webViewError.takeIf { it.isNotEmpty() }?.let { append("\n\n").append(it) }
},
- onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
+ onSubmit = { state.eventSink(CallScreenEvent.Hangup) },
)
} else {
var webViewAudioManager by remember { mutableStateOf(null) }
@@ -123,16 +123,16 @@ internal fun CallScreenView(
Timber.d("Can't start in-call audio mode since the app is already in it.")
}
},
- onError = { state.eventSink(CallScreenEvents.OnWebViewError(it)) },
+ onError = { state.eventSink(CallScreenEvent.OnWebViewError(it)) },
)
webViewAudioManager = WebViewAudioManager(
webView = webView,
coroutineScope = coroutineScope,
onInvalidAudioDeviceAdded = { invalidAudioDeviceReason = it },
)
- state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
+ state.eventSink(CallScreenEvent.SetupMessageChannels(interceptor))
val pipController = WebViewPipController(webView)
- pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
+ pipState.eventSink(PictureInPictureEvent.SetPipController(pipController))
},
onDestroyWebView = {
// Reset audio mode
@@ -147,7 +147,7 @@ internal fun CallScreenView(
Timber.e(state.urlState.error, "WebView failed to load URL: ${state.urlState.error.message}")
ErrorDialog(
content = state.urlState.error.message.orEmpty(),
- onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
+ onSubmit = { state.eventSink(CallScreenEvent.Hangup) },
)
}
is AsyncData.Success -> Unit
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallTypeExtension.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallTypeExtension.kt
deleted file mode 100644
index 0c18c3e1a4..0000000000
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallTypeExtension.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (c) 2025 Element Creations Ltd.
- * 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.features.call.impl.ui
-
-import io.element.android.features.call.api.CallType
-import io.element.android.libraries.matrix.api.core.SessionId
-
-fun CallType.getSessionId(): SessionId? {
- return when (this) {
- is CallType.ExternalUrl -> null
- is CallType.RoomCall -> sessionId
- }
-}
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt
index 5fa3beb36a..367328ed10 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt
@@ -35,16 +35,14 @@ import androidx.core.util.Consumer
import androidx.lifecycle.Lifecycle
import dev.zacsweers.metro.Inject
import io.element.android.compound.colors.SemanticColorsLightDark
-import io.element.android.features.call.api.CallType
-import io.element.android.features.call.api.CallType.ExternalUrl
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
import io.element.android.features.call.impl.di.CallBindings
-import io.element.android.features.call.impl.pip.PictureInPictureEvents
+import io.element.android.features.call.impl.pip.PictureInPictureEvent
import io.element.android.features.call.impl.pip.PictureInPicturePresenter
import io.element.android.features.call.impl.pip.PictureInPictureState
import io.element.android.features.call.impl.pip.PipView
import io.element.android.features.call.impl.services.CallForegroundService
-import io.element.android.features.call.impl.utils.CallIntentDataParser
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.androidutils.browser.ConsoleMessageLogger
import io.element.android.libraries.architecture.Presenter
@@ -64,7 +62,6 @@ class ElementCallActivity :
AppCompatActivity(),
CallScreenNavigator,
PipView {
- @Inject lateinit var callIntentDataParser: CallIntentDataParser
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
@Inject lateinit var appPreferencesStore: AppPreferencesStore
@Inject lateinit var featureFlagService: FeatureFlagService
@@ -80,9 +77,9 @@ class ElementCallActivity :
private val requestPermissionsLauncher = registerPermissionResultLauncher()
- private val webViewTarget = mutableStateOf(null)
+ private val webViewTarget = mutableStateOf(null)
- private var eventSink: ((CallScreenEvents) -> Unit)? = null
+ private var eventSink: ((CallScreenEvent) -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -98,7 +95,7 @@ class ElementCallActivity :
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
}
- setCallType(intent)
+ setCallData(intent)
// If presenter is not created at this point, it means we have no call to display, the Activity is finishing, so return early
if (!::presenter.isInitialized) {
return
@@ -111,8 +108,8 @@ class ElementCallActivity :
setContent {
val pipState = pictureInPicturePresenter.present()
ListenToAndroidEvents(pipState)
- val colors by remember(webViewTarget.value?.getSessionId()) {
- enterpriseService.semanticColorsFlow(sessionId = webViewTarget.value?.getSessionId())
+ val colors by remember(webViewTarget.value?.sessionId) {
+ enterpriseService.semanticColorsFlow(sessionId = webViewTarget.value?.sessionId)
}.collectAsState(SemanticColorsLightDark.default)
ElementThemeApp(
appPreferencesStore = appPreferencesStore,
@@ -123,9 +120,8 @@ class ElementCallActivity :
) {
val state = presenter.present()
eventSink = state.eventSink
- LaunchedEffect(state.isCallActive, state.isInWidgetMode) {
- // Note when not in WidgetMode, isCallActive will never be true, so consider the call is active
- if (state.isCallActive || !state.isInWidgetMode) {
+ LaunchedEffect(state.isCallActive) {
+ if (state.isCallActive) {
setCallIsActive()
}
}
@@ -163,7 +159,7 @@ class ElementCallActivity :
if (requestPermissionCallback != null) {
Timber.tag(loggerTag.value).w("Ignoring onUserLeaveHint event because user is asked to grant permissions")
} else {
- pipEventSink(PictureInPictureEvents.EnterPictureInPicture)
+ pipEventSink(PictureInPictureEvent.EnterPictureInPicture)
}
}
addOnUserLeaveHintListener(listener)
@@ -173,10 +169,10 @@ class ElementCallActivity :
}
DisposableEffect(Unit) {
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
- pipEventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(isInPictureInPictureMode))
+ pipEventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(isInPictureInPictureMode))
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call")
- eventSink?.invoke(CallScreenEvents.Hangup)
+ eventSink?.invoke(CallScreenEvent.Hangup)
}
}
addOnPictureInPictureModeChangedListener(onPictureInPictureModeChangedListener)
@@ -188,7 +184,7 @@ class ElementCallActivity :
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
- setCallType(intent)
+ setCallData(intent)
}
override fun onDestroy() {
@@ -207,25 +203,24 @@ class ElementCallActivity :
finish()
}
- private fun setCallType(intent: Intent?) {
- val callType = intent?.let {
- IntentCompat.getParcelableExtra(intent, DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, CallType::class.java)
- ?: intent.dataString?.let(::parseUrl)?.let(::ExternalUrl)
+ private fun setCallData(intent: Intent?) {
+ val callData = intent?.let {
+ IntentCompat.getParcelableExtra(intent, DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, CallData::class.java)
}
- val currentCallType = webViewTarget.value
- if (currentCallType == null) {
- if (callType == null) {
+ val currentCallData = webViewTarget.value
+ if (currentCallData == null) {
+ if (callData == null) {
Timber.tag(loggerTag.value).d("Re-opened the activity but we have no url to load or a cached one, finish the activity")
finish()
} else {
Timber.tag(loggerTag.value).d("Set the call type and create the presenter")
- webViewTarget.value = callType
- presenter = presenterFactory.create(callType, this)
+ webViewTarget.value = callData
+ presenter = presenterFactory.create(callData, this)
}
} else {
- if (callType == null) {
+ if (callData == null) {
Timber.tag(loggerTag.value).d("Coming back from notification, do nothing")
- } else if (callType != currentCallType) {
+ } else if (callData != currentCallData) {
Timber.tag(loggerTag.value).d("User starts another call, restart the Activity")
setIntent(intent)
recreate()
@@ -236,8 +231,6 @@ class ElementCallActivity :
}
}
- private fun parseUrl(url: String?): String? = callIntentDataParser.parse(url)
-
private fun registerPermissionResultLauncher(): ActivityResultLauncher> {
return registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
@@ -287,7 +280,7 @@ class ElementCallActivity :
}
override fun hangUp() {
- eventSink?.invoke(CallScreenEvents.Hangup)
+ eventSink?.invoke(CallScreenEvent.Hangup)
}
}
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt
index 2c4deab65e..1d6989fb3c 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt
@@ -19,7 +19,7 @@ import androidx.core.content.IntentCompat
import androidx.lifecycle.lifecycleScope
import dev.zacsweers.metro.Inject
import io.element.android.compound.colors.SemanticColorsLightDark
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.features.call.impl.di.CallBindings
import io.element.android.features.call.impl.notifications.CallNotificationData
@@ -118,10 +118,10 @@ class IncomingCallActivity : AppCompatActivity() {
private fun onAnswer(notificationData: CallNotificationData) {
elementCallEntryPoint.startCall(
- CallType.RoomCall(
- notificationData.sessionId,
- notificationData.roomId,
- isAudioCall = notificationData.audioOnly
+ CallData(
+ sessionId = notificationData.sessionId,
+ roomId = notificationData.roomId,
+ isAudioCall = notificationData.audioOnly,
)
)
}
@@ -129,7 +129,7 @@ class IncomingCallActivity : AppCompatActivity() {
private fun onCancel() {
val activeCall = activeCallManager.activeCall.value ?: return
appCoroutineScope.launch {
- activeCallManager.hangUpCall(callType = activeCall.callType)
+ activeCallManager.hangUpCall(callData = activeCall.callData)
}
}
}
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt
index 99679a8afb..685fc932fe 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt
@@ -20,7 +20,7 @@ import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.SingleIn
import io.element.android.appconfig.ElementCallConfig
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.api.CurrentCall
import io.element.android.features.call.impl.notifications.CallNotificationData
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
@@ -73,20 +73,20 @@ interface ActiveCallManager {
/**
* Called to hang up the active call. It will hang up the call and remove any existing UI and the active call.
- * @param callType The type of call that the user hangs up, either an external url one or a room one.
+ * @param callData The data about the call.
* @param notificationData The data for the incoming call notification.
*/
suspend fun hangUpCall(
- callType: CallType,
+ callData: CallData,
notificationData: CallNotificationData? = null,
)
/**
* Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall].
*
- * @param callType The type of call that the user joined, either an external url one or a room one.
+ * @param callData The data about the call.
*/
- suspend fun joinedCall(callType: CallType)
+ suspend fun joinedCall(callData: CallData)
}
@SingleIn(AppScope::class)
@@ -143,7 +143,7 @@ class DefaultActiveCallManager(
return
}
activeCall.value = ActiveCall(
- callType = CallType.RoomCall(
+ callData = CallData(
sessionId = notificationData.sessionId,
roomId = notificationData.roomId,
isAudioCall = notificationData.audioOnly,
@@ -198,17 +198,17 @@ class DefaultActiveCallManager(
}
override suspend fun hangUpCall(
- callType: CallType,
+ callData: CallData,
notificationData: CallNotificationData?,
) = mutex.withLock {
- Timber.tag(tag).d("Hang up call: $callType")
+ Timber.tag(tag).d("Hang up call: $callData")
cancelIncomingCallNotification()
val currentActiveCall = activeCall.value ?: run {
// activeCall.value can be null if the application has been killed while the call was ringing
// Build a currentActiveCall with the provided parameters.
notificationData?.let {
ActiveCall(
- callType = callType,
+ callData = callData,
callState = CallState.Ringing(
notificationData = notificationData,
)
@@ -219,8 +219,8 @@ class DefaultActiveCallManager(
return@withLock
}
- if (currentActiveCall.callType != callType) {
- Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring")
+ if (currentActiveCall.callData != callData) {
+ Timber.tag(tag).w("Call type $callData does not match the active call type, ignoring")
return@withLock
}
if (currentActiveCall.callState is CallState.Ringing) {
@@ -244,8 +244,8 @@ class DefaultActiveCallManager(
activeCall.value = null
}
- override suspend fun joinedCall(callType: CallType) = mutex.withLock {
- Timber.tag(tag).d("Joined call: $callType")
+ override suspend fun joinedCall(callData: CallData) = mutex.withLock {
+ Timber.tag(tag).d("Joined call: $callData")
cancelIncomingCallNotification()
if (activeWakeLock?.isHeld == true) {
Timber.tag(tag).d("Releasing partial wakelock after joining call")
@@ -254,7 +254,7 @@ class DefaultActiveCallManager(
timedOutCallJob?.cancel()
activeCall.value = ActiveCall(
- callType = callType,
+ callData = callData,
callState = CallState.InCall,
)
}
@@ -307,15 +307,15 @@ class DefaultActiveCallManager(
private fun observeRingingCall() {
activeCall
.filterNotNull()
- .filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
+ .filter { it.callState is CallState.Ringing }
.flatMapLatest { activeCall ->
- val callType = activeCall.callType as CallType.RoomCall
+ val callData = activeCall.callData
val ringingInfo = activeCall.callState as CallState.Ringing
- val client = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull() ?: run {
+ val client = matrixClientProvider.getOrRestore(callData.sessionId).getOrNull() ?: run {
Timber.tag(tag).d("Couldn't find session for incoming call: $activeCall")
return@flatMapLatest flowOf()
}
- val room = client.getRoom(callType.roomId) ?: run {
+ val room = client.getRoom(callData.roomId) ?: run {
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
return@flatMapLatest flowOf()
}
@@ -346,17 +346,17 @@ class DefaultActiveCallManager(
// has joined the call from another session.
activeCall
.filterNotNull()
- .filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
+ .filter { it.callState is CallState.Ringing }
.flatMapLatest { activeCall ->
- val callType = activeCall.callType as CallType.RoomCall
+ val callData = activeCall.callData
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
- val room = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()?.getRoom(callType.roomId) ?: run {
+ val room = matrixClientProvider.getOrRestore(callData.sessionId).getOrNull()?.getRoom(callData.roomId) ?: run {
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
return@flatMapLatest flowOf()
}
room.roomInfoFlow.map {
Timber.tag(tag).d("Has room call status changed for ringing call: ${it.hasRoomCall}")
- it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
+ it.hasRoomCall to (callData.sessionId in it.activeRoomCallParticipants)
}
}
// We only want to check if the room active call status changes
@@ -388,10 +388,7 @@ class DefaultActiveCallManager(
// Nothing to do
}
is CallState.InCall -> {
- when (val callType = value.callType) {
- is CallType.ExternalUrl -> defaultCurrentCallService.onCallStarted(CurrentCall.ExternalUrl(callType.url))
- is CallType.RoomCall -> defaultCurrentCallService.onCallStarted(CurrentCall.RoomCall(callType.roomId))
- }
+ defaultCurrentCallService.onCallStarted(CurrentCall.RoomCall(value.callData.roomId))
}
}
}
@@ -404,7 +401,7 @@ class DefaultActiveCallManager(
* Represents an active call.
*/
data class ActiveCall(
- val callType: CallType,
+ val callData: CallData,
val callState: CallState,
)
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallIntentDataParser.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallIntentDataParser.kt
deleted file mode 100644
index f5433c15a0..0000000000
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallIntentDataParser.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (c) 2025 Element Creations Ltd.
- * Copyright 2023-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.features.call.impl.utils
-
-import android.net.Uri
-import androidx.core.net.toUri
-import dev.zacsweers.metro.Inject
-
-@Inject
-class CallIntentDataParser {
- private val validHttpSchemes = sequenceOf("https")
- private val knownHosts = sequenceOf(
- "call.element.io",
- )
-
- fun parse(data: String?): String? {
- val parsedUrl = data?.toUri() ?: return null
- val scheme = parsedUrl.scheme
- return when {
- scheme in validHttpSchemes -> parsedUrl
- scheme == "element" && parsedUrl.host == "call" -> {
- parsedUrl.getUrlParameter()
- }
- scheme == "io.element.call" && parsedUrl.host == null -> {
- parsedUrl.getUrlParameter()
- }
- // This should never be possible, but we still need to take into account the possibility
- else -> null
- }
- ?.takeIf { it.host in knownHosts }
- ?.withCustomParameters()
- }
-
- private fun Uri.getUrlParameter(): Uri? {
- return getQueryParameter("url")
- ?.let { urlParameter ->
- urlParameter.toUri().takeIf { uri ->
- uri.scheme in validHttpSchemes && !uri.host.isNullOrBlank()
- }
- }
- }
-}
-
-/**
- * Ensure the uri has the following parameters and value in the fragment:
- * - appPrompt=false
- * - confineToRoom=true
- * to ensure that the rendering will bo correct on the embedded Webview.
- */
-private fun Uri.withCustomParameters(): String {
- val builder = buildUpon()
- // Remove the existing query parameters
- builder.clearQuery()
- queryParameterNames.forEach {
- if (it == APP_PROMPT_PARAMETER || it == CONFINE_TO_ROOM_PARAMETER) return@forEach
- builder.appendQueryParameter(it, getQueryParameter(it))
- }
- // Remove the existing fragment parameters, and build the new fragment
- val currentFragment = fragment ?: ""
- // Reset the current fragment
- builder.fragment("")
- val queryFragmentPosition = currentFragment.lastIndexOf("?")
- val newFragment = if (queryFragmentPosition == -1) {
- // No existing query, build it.
- "$currentFragment?$APP_PROMPT_PARAMETER=false&$CONFINE_TO_ROOM_PARAMETER=true"
- } else {
- buildString {
- append(currentFragment.substring(0, queryFragmentPosition + 1))
- val queryFragment = currentFragment.substring(queryFragmentPosition + 1)
- // Replace the existing parameters
- val newQueryFragment = queryFragment
- .replace("$APP_PROMPT_PARAMETER=true", "$APP_PROMPT_PARAMETER=false")
- .replace("$CONFINE_TO_ROOM_PARAMETER=false", "$CONFINE_TO_ROOM_PARAMETER=true")
- append(newQueryFragment)
- // Ensure the parameters are there
- if (!newQueryFragment.contains("$APP_PROMPT_PARAMETER=false")) {
- if (newQueryFragment.isNotEmpty()) {
- append("&")
- }
- append("$APP_PROMPT_PARAMETER=false")
- }
- if (!newQueryFragment.contains("$CONFINE_TO_ROOM_PARAMETER=true")) {
- append("&$CONFINE_TO_ROOM_PARAMETER=true")
- }
- }
- }
- // We do not want to encode the Fragment part, so append it manually
- return builder.build().toString() + "#" + newFragment
-}
-
-private const val APP_PROMPT_PARAMETER = "appPrompt"
-private const val CONFINE_TO_ROOM_PARAMETER = "confineToRoom"
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/IntentProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/IntentProvider.kt
index 0f74ba86d4..c6c607cbbc 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/IntentProvider.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/IntentProvider.kt
@@ -12,21 +12,21 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.PendingIntentCompat
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
import io.element.android.features.call.impl.ui.ElementCallActivity
internal object IntentProvider {
- fun createIntent(context: Context, callType: CallType): Intent = Intent(context, ElementCallActivity::class.java).apply {
- putExtra(DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, callType)
+ fun createIntent(context: Context, callData: CallData): Intent = Intent(context, ElementCallActivity::class.java).apply {
+ putExtra(DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, callData)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)
}
- fun getPendingIntent(context: Context, callType: CallType): PendingIntent {
+ fun getPendingIntent(context: Context, callData: CallData): PendingIntent {
return PendingIntentCompat.getActivity(
context,
DefaultElementCallEntryPoint.REQUEST_CODE,
- createIntent(context, callType),
+ createIntent(context, callData),
PendingIntent.FLAG_CANCEL_CURRENT,
false
)!!
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewWidgetMessageInterceptor.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewWidgetMessageInterceptor.kt
index f7ab2c57af..c74ae90abd 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewWidgetMessageInterceptor.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewWidgetMessageInterceptor.kt
@@ -140,26 +140,33 @@ class WebViewWidgetMessageInterceptor(
}
}
- // Create a WebMessageListener, which will receive messages from the WebView and reply to them
- val webMessageListener = WebViewCompat.WebMessageListener { _, message, _, _, _ ->
- onMessageReceived(message.data)
- }
+ // Always register JavascriptInterface as the baseline message channel.
+ // This works on all WebView implementations including Huawei.
+ webView.addJavascriptInterface(object {
+ @JavascriptInterface
+ fun postMessage(json: String?) {
+ onMessageReceived(json)
+ }
+ }, LISTENER_NAME)
- // Use WebMessageListener if supported, otherwise use JavascriptInterface
- if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
+ // Additionally register WebMessageListener on WebViews that reliably support it.
+ // Huawei WebView (Chromium < 119) reports WEB_MESSAGE_LISTENER as supported
+ // but silently drops messages, so we only trust it on Chromium 119+.
+ // See: https://github.com/element-hq/element-x-android/issues/6632
+ val webViewVersionName = WebViewCompat.getCurrentWebViewPackage(webView.context)?.versionName.orEmpty()
+ Timber.d("Using WebView version: $webViewVersionName")
+ val webViewVersionCode = webViewVersionName.split(".").firstOrNull()?.toIntOrNull() ?: 0
+
+ if (webViewVersionCode >= 119 &&
+ WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
WebViewCompat.addWebMessageListener(
webView,
LISTENER_NAME,
setOf("*"),
- webMessageListener
- )
- } else {
- webView.addJavascriptInterface(object {
- @JavascriptInterface
- fun postMessage(json: String?) {
- onMessageReceived(json)
+ WebViewCompat.WebMessageListener { _, message, _, _, _ ->
+ onMessageReceived(message.data)
}
- }, LISTENER_NAME)
+ )
}
}
diff --git a/features/call/impl/src/main/res/values-zh/translations.xml b/features/call/impl/src/main/res/values-zh/translations.xml
index 6192568a61..7a81fde819 100644
--- a/features/call/impl/src/main/res/values-zh/translations.xml
+++ b/features/call/impl/src/main/res/values-zh/translations.xml
@@ -1,8 +1,8 @@
"通话进行中"
- "点按即可返回通话"
+ "点击以返回通话"
"☎️ 通话中"
- "Element Call 不支持在此 Android 版本中使用蓝牙音频设备。请选择其他音频设备。"
- "Element 来电"
+ "Element Call 不支持在此 Android 版本中使用蓝牙音频设备。请选择其它音频设备。"
+ "Element Call 来电"
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt
index 85cec8c586..f21447cc85 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt
@@ -11,7 +11,7 @@ package io.element.android.features.call
import android.content.Intent
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
import io.element.android.features.call.impl.notifications.CallNotificationData
import io.element.android.features.call.impl.ui.ElementCallActivity
@@ -37,7 +37,7 @@ class DefaultElementCallEntryPointTest {
@Test
fun `startCall - starts ElementCallActivity setup with the needed extras`() = runTest {
val entryPoint = createEntryPoint()
- entryPoint.startCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, isAudioCall = false))
+ entryPoint.startCall(CallData(A_SESSION_ID, A_ROOM_ID, isAudioCall = false))
val expectedIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, ElementCallActivity::class.java)
val intent = shadowOf(RuntimeEnvironment.getApplication()).nextStartedActivity
@@ -53,7 +53,7 @@ class DefaultElementCallEntryPointTest {
val entryPoint = createEntryPoint(activeCallManager = activeCallManager)
entryPoint.handleIncomingCall(
- callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, isAudioCall = false),
+ callData = CallData(A_SESSION_ID, A_ROOM_ID, isAudioCall = false),
eventId = AN_EVENT_ID,
senderId = A_USER_ID_2,
roomName = "roomName",
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt
index c087fa3c35..c3d7fdf17b 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt
@@ -8,11 +8,9 @@
package io.element.android.features.call.impl.pip
-import app.cash.molecule.RecompositionMode
-import app.cash.molecule.moleculeFlow
-import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.tests.testutils.lambda.lambdaRecorder
+import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -20,9 +18,7 @@ class PictureInPicturePresenterTest {
@Test
fun `when pip is not supported, the state value supportPip is false`() = runTest {
val presenter = createPictureInPicturePresenter(supportPip = false)
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
val initialState = awaitItem()
assertThat(initialState.supportPip).isFalse()
}
@@ -35,9 +31,7 @@ class PictureInPicturePresenterTest {
supportPip = true,
pipView = FakePipView(setPipParamsResult = { }),
)
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
val initialState = awaitItem()
assertThat(initialState.supportPip).isTrue()
}
@@ -53,18 +47,16 @@ class PictureInPicturePresenterTest {
enterPipModeResult = enterPipModeResult,
),
)
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
val initialState = awaitItem()
assertThat(initialState.isInPictureInPicture).isFalse()
- initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
+ initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
enterPipModeResult.assertions().isCalledOnce()
- initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
+ initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(true))
val pipState = awaitItem()
assertThat(pipState.isInPictureInPicture).isTrue()
// User stops pip
- initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
+ initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(false))
val finalState = awaitItem()
assertThat(finalState.isInPictureInPicture).isFalse()
}
@@ -80,12 +72,10 @@ class PictureInPicturePresenterTest {
handUpResult = handUpResult
),
)
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
val initialState = awaitItem()
- initialState.eventSink(PictureInPictureEvents.SetPipController(FakePipController(canEnterPipResult = { false })))
- initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
+ initialState.eventSink(PictureInPictureEvent.SetPipController(FakePipController(canEnterPipResult = { false })))
+ initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
handUpResult.assertions().isCalledOnce()
}
}
@@ -102,12 +92,10 @@ class PictureInPicturePresenterTest {
enterPipModeResult = enterPipModeResult
),
)
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
val initialState = awaitItem()
initialState.eventSink(
- PictureInPictureEvents.SetPipController(
+ PictureInPictureEvent.SetPipController(
FakePipController(
canEnterPipResult = { true },
enterPipResult = enterPipResult,
@@ -115,16 +103,16 @@ class PictureInPicturePresenterTest {
)
)
)
- initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
+ initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
enterPipModeResult.assertions().isCalledOnce()
enterPipResult.assertions().isNeverCalled()
- initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
+ initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(true))
val pipState = awaitItem()
assertThat(pipState.isInPictureInPicture).isTrue()
enterPipResult.assertions().isCalledOnce()
// User stops pip
exitPipResult.assertions().isNeverCalled()
- initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
+ initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(false))
val finalState = awaitItem()
assertThat(finalState.isInPictureInPicture).isFalse()
exitPipResult.assertions().isCalledOnce()
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallDataTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallDataTest.kt
new file mode 100644
index 0000000000..f0cdd44082
--- /dev/null
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallDataTest.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2025 Element Creations Ltd.
+ * 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.features.call.ui
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.features.call.api.CallData
+import io.element.android.libraries.matrix.test.A_ROOM_ID
+import io.element.android.libraries.matrix.test.A_SESSION_ID
+import org.junit.Test
+
+class CallDataTest {
+ @Test
+ fun `RoomCall stringification does not contain the URL`() {
+ assertThat(CallData(A_SESSION_ID, A_ROOM_ID, false).toString())
+ .isEqualTo("CallData(sessionId=$A_SESSION_ID, roomId=$A_ROOM_ID, isAudioCall=false)")
+ }
+}
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
index b6b0120451..276e6670f1 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
@@ -13,8 +13,8 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.MobileScreen
-import io.element.android.features.call.api.CallType
-import io.element.android.features.call.impl.ui.CallScreenEvents
+import io.element.android.features.call.api.CallData
+import io.element.android.features.call.impl.ui.CallScreenEvent
import io.element.android.features.call.impl.ui.CallScreenNavigator
import io.element.android.features.call.impl.ui.CallScreenPresenter
import io.element.android.features.call.impl.utils.WidgetMessageSerializer
@@ -39,6 +39,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
+import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelAndJoin
@@ -59,46 +60,19 @@ class CallScreenPresenterTest {
val warmUpRule = WarmUpRule()
@Test
- fun `present - with CallType ExternalUrl just loads the URL and sets the call as active`() = runTest {
- val analyticsLambda = lambdaRecorder {}
- val joinedCallLambda = lambdaRecorder {}
- val presenter = createCallScreenPresenter(
- callType = CallType.ExternalUrl("https://call.element.io"),
- screenTracker = FakeScreenTracker(analyticsLambda),
- activeCallManager = FakeActiveCallManager(joinedCallResult = joinedCallLambda),
- )
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
- // Wait until the URL is loaded
- advanceTimeBy(1.seconds)
- skipItems(2)
- val initialState = awaitItem()
- assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
- assertThat(initialState.webViewError).isNull()
- assertThat(initialState.isInWidgetMode).isFalse()
- assertThat(initialState.isCallActive).isFalse()
- analyticsLambda.assertions().isNeverCalled()
- joinedCallLambda.assertions().isCalledOnce()
- }
- }
-
- @Test
- fun `present - with CallType RoomCall sets call as active, loads URL and runs WidgetDriver`() = runTest {
+ fun `present - with CallData sets call as active, loads URL and runs WidgetDriver`() = runTest {
val widgetDriver = FakeMatrixWidgetDriver()
val widgetProvider = FakeCallWidgetProvider(widgetDriver)
val analyticsLambda = lambdaRecorder {}
- val joinedCallLambda = lambdaRecorder {}
+ val joinedCallLambda = lambdaRecorder {}
val presenter = createCallScreenPresenter(
- callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
+ callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
widgetDriver = widgetDriver,
widgetProvider = widgetProvider,
screenTracker = FakeScreenTracker(analyticsLambda),
activeCallManager = FakeActiveCallManager(joinedCallResult = joinedCallLambda),
)
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
// Wait until the URL is loaded
advanceTimeBy(1.seconds)
skipItems(1)
@@ -107,7 +81,6 @@ class CallScreenPresenterTest {
val initialState = awaitItem()
assertThat(initialState.urlState).isInstanceOf(AsyncData.Loading::class.java)
assertThat(initialState.isCallActive).isFalse()
- assertThat(initialState.isInWidgetMode).isTrue()
assertThat(widgetProvider.getWidgetCalled).isTrue()
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
analyticsLambda.assertions().isCalledOnce().with(value(MobileScreen.ScreenName.RoomCall))
@@ -123,19 +96,17 @@ class CallScreenPresenterTest {
fun `present - set message interceptor, send and receive messages`() = runTest {
val widgetDriver = FakeMatrixWidgetDriver()
val presenter = createCallScreenPresenter(
- callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
+ callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
widgetDriver = widgetDriver,
screenTracker = FakeScreenTracker {},
)
val messageInterceptor = FakeWidgetMessageInterceptor()
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
// Give it time to load the URL and WidgetDriver
advanceTimeBy(1.seconds)
val initialState = awaitItem()
- initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
+ initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
// And incoming message from the Widget Driver is passed to the WebView
widgetDriver.givenIncomingMessage("A message")
@@ -154,24 +125,22 @@ class CallScreenPresenterTest {
val navigator = FakeCallScreenNavigator()
val widgetDriver = FakeMatrixWidgetDriver()
val presenter = createCallScreenPresenter(
- callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
+ callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
screenTracker = FakeScreenTracker {},
)
val messageInterceptor = FakeWidgetMessageInterceptor()
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
val initialState = awaitItem()
// Give it time to load the URL and WidgetDriver
advanceTimeBy(1.seconds)
- initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
+ initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
- initialState.eventSink(CallScreenEvents.Hangup)
+ initialState.eventSink(CallScreenEvent.Hangup)
// Let background coroutines run and the widget drive be received
runCurrent()
@@ -188,22 +157,20 @@ class CallScreenPresenterTest {
val navigator = FakeCallScreenNavigator()
val widgetDriver = FakeMatrixWidgetDriver()
val presenter = createCallScreenPresenter(
- callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
+ callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
screenTracker = FakeScreenTracker {},
)
val messageInterceptor = FakeWidgetMessageInterceptor()
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
val initialState = awaitItem()
// Give it time to load the URL and WidgetDriver
advanceTimeBy(1.seconds)
- initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
+ initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
messageInterceptor.givenInterceptedMessage("""{"action":"io.element.close","api":"fromWidget","widgetId":"1","requestId":"1"}""")
@@ -223,22 +190,20 @@ class CallScreenPresenterTest {
val navigator = FakeCallScreenNavigator()
val widgetDriver = FakeMatrixWidgetDriver()
val presenter = createCallScreenPresenter(
- callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
+ callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
screenTracker = FakeScreenTracker {},
)
val messageInterceptor = FakeWidgetMessageInterceptor()
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
// Give it time to load the URL and WidgetDriver
advanceTimeBy(1.seconds)
skipItems(2)
val initialState = awaitItem()
assertThat(initialState.isCallActive).isFalse()
- initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
+ initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
messageInterceptor.givenInterceptedMessage(
"""
{
@@ -260,22 +225,20 @@ class CallScreenPresenterTest {
val navigator = FakeCallScreenNavigator()
val widgetDriver = FakeMatrixWidgetDriver()
val presenter = createCallScreenPresenter(
- callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
+ callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
screenTracker = FakeScreenTracker {},
)
val messageInterceptor = FakeWidgetMessageInterceptor()
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
+ presenter.test {
// Give it time to load the URL and WidgetDriver
advanceTimeBy(1.seconds)
skipItems(2)
val initialState = awaitItem()
assertThat(initialState.isCallActive).isFalse()
- initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
+ initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
skipItems(2)
// Wait for the timeout to trigger
@@ -300,7 +263,7 @@ class CallScreenPresenterTest {
val matrixClient = FakeMatrixClient(syncService = syncService)
val appForegroundStateService = FakeAppForegroundStateService()
val presenter = createCallScreenPresenter(
- callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
+ callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
@@ -338,53 +301,8 @@ class CallScreenPresenterTest {
}
}
- @Test
- fun `present - error from WebView are updating the state`() = runTest {
- val presenter = createCallScreenPresenter(
- callType = CallType.ExternalUrl("https://call.element.io"),
- activeCallManager = FakeActiveCallManager(),
- )
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
- // Wait until the URL is loaded
- advanceTimeBy(1.seconds)
- skipItems(2)
- val initialState = awaitItem()
- initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
- val finalState = awaitItem()
- assertThat(finalState.webViewError).isEqualTo("A Webview error")
- }
- }
-
- @Test
- fun `present - error from WebView are ignored if Element Call is loaded`() = runTest {
- val presenter = createCallScreenPresenter(
- callType = CallType.ExternalUrl("https://call.element.io"),
- activeCallManager = FakeActiveCallManager(),
- )
- moleculeFlow(RecompositionMode.Immediate) {
- presenter.present()
- }.test {
- // Wait until the URL is loaded
- skipItems(1)
- val initialState = awaitItem()
-
- val messageInterceptor = FakeWidgetMessageInterceptor()
- initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
- // Emit a message
- messageInterceptor.givenInterceptedMessage("A message")
- // WebView emits an error, but it will be ignored
- initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
- val finalState = awaitItem()
- assertThat(finalState.webViewError).isNull()
-
- cancelAndIgnoreRemainingEvents()
- }
- }
-
private fun TestScope.createCallScreenPresenter(
- callType: CallType,
+ callData: CallData,
navigator: CallScreenNavigator = FakeCallScreenNavigator(),
widgetDriver: FakeMatrixWidgetDriver = FakeMatrixWidgetDriver(),
widgetProvider: FakeCallWidgetProvider = FakeCallWidgetProvider(widgetDriver),
@@ -401,7 +319,7 @@ class CallScreenPresenterTest {
}
val clock = SystemClock { 0 }
return CallScreenPresenter(
- callType = callType,
+ callData = callData,
navigator = navigator,
callWidgetProvider = widgetProvider,
userAgentProvider = userAgentProvider,
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallTypeTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallTypeTest.kt
deleted file mode 100644
index c83408bd3b..0000000000
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallTypeTest.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2025 Element Creations Ltd.
- * 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.features.call.ui
-
-import com.google.common.truth.Truth.assertThat
-import io.element.android.features.call.api.CallType
-import io.element.android.features.call.impl.ui.getSessionId
-import io.element.android.libraries.matrix.test.A_ROOM_ID
-import io.element.android.libraries.matrix.test.A_SESSION_ID
-import org.junit.Test
-
-class CallTypeTest {
- @Test
- fun `getSessionId returns null for ExternalUrl`() {
- assertThat(CallType.ExternalUrl("aURL").getSessionId()).isNull()
- }
-
- @Test
- fun `getSessionId returns the sessionId for RoomCall`() {
- assertThat(
- CallType.RoomCall(
- sessionId = A_SESSION_ID,
- roomId = A_ROOM_ID,
- isAudioCall = false,
- ).getSessionId()
- ).isEqualTo(A_SESSION_ID)
- }
-
- @Test
- fun `ExternalUrl stringification does not contain the URL`() {
- assertThat(CallType.ExternalUrl("aURL").toString()).isEqualTo("ExternalUrl")
- }
-
- @Test
- fun `RoomCall stringification does not contain the URL`() {
- assertThat(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false).toString())
- .isEqualTo("RoomCall(sessionId=$A_SESSION_ID, roomId=$A_ROOM_ID, isAudioCall=false)")
- }
-}
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt
deleted file mode 100644
index 43f7f931f1..0000000000
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (c) 2025 Element Creations Ltd.
- * Copyright 2023-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.features.call.utils
-
-import com.google.common.truth.Truth.assertThat
-import io.element.android.features.call.impl.utils.CallIntentDataParser
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-import java.net.URLEncoder
-
-@RunWith(RobolectricTestRunner::class)
-class CallIntentDataParserTest {
- private val callIntentDataParser = CallIntentDataParser()
-
- @Test
- fun `a null data returns null`() {
- val url: String? = null
- assertThat(callIntentDataParser.parse(url)).isNull()
- }
-
- @Test
- fun `empty data returns null`() {
- doTest("", null)
- }
-
- @Test
- fun `invalid data returns null`() {
- doTest("!", null)
- }
-
- @Test
- fun `data with no scheme returns null`() {
- doTest("test", null)
- }
-
- @Test
- fun `Element Call http urls returns null`() {
- doTest("http://call.element.io", null)
- doTest("http://call.element.io/some-actual-call?with=parameters", null)
- }
-
- @Test
- fun `Element Call urls with unknown host returns null`() {
- // Check valid host first, should not return null
- doTest("https://call.element.io", "https://call.element.io#?appPrompt=false&confineToRoom=true")
- // Unknown host should return null
- doTest("https://unknown.io", null)
- doTest("https://call.unknown.io", null)
- doTest("https://call.element.com", null)
- doTest("https://call.element.io.tld", null)
- }
-
- @Test
- fun `Element Call urls will be returned as is`() {
- doTest(
- url = "https://call.element.io",
- expectedResult = "https://call.element.io#?$EXTRA_PARAMS"
- )
- }
-
- @Test
- fun `Element Call url with url param gets url extracted`() {
- doTest(
- url = VALID_CALL_URL_WITH_PARAM,
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
- )
- }
-
- @Test
- fun `HTTP and HTTPS urls that don't come from EC return null`() {
- doTest("http://app.element.io", null)
- doTest("https://app.element.io", null)
- doTest("http://", null)
- doTest("https://", null)
- }
-
- @Test
- fun `Element Call url with no url returns null`() {
- val embeddedUrl = VALID_CALL_URL_WITH_PARAM
- val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
- val url = "io.element.call:/?no_url=$encodedUrl"
- assertThat(callIntentDataParser.parse(url)).isNull()
- }
-
- @Test
- fun `element scheme with no call host returns null`() {
- val embeddedUrl = VALID_CALL_URL_WITH_PARAM
- val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
- val url = "element://no-call?url=$encodedUrl"
- assertThat(callIntentDataParser.parse(url)).isNull()
- }
-
- @Test
- fun `element scheme with no data returns null`() {
- val url = "element://call?url="
- assertThat(callIntentDataParser.parse(url)).isNull()
- }
-
- @Test
- fun `Element Call url with no data returns null`() {
- val url = "io.element.call:/?url="
- assertThat(callIntentDataParser.parse(url)).isNull()
- }
-
- @Test
- fun `element invalid scheme returns null`() {
- val embeddedUrl = VALID_CALL_URL_WITH_PARAM
- val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
- val url = "bad.scheme:/?url=$encodedUrl"
- assertThat(callIntentDataParser.parse(url)).isNull()
- }
-
- @Test
- fun `Element Call url with url extra param appPrompt gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM&appPrompt=true",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
- )
- }
-
- @Test
- fun `Element Call url with url extra param in fragment appPrompt gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=true",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&confineToRoom=true"
- )
- }
-
- @Test
- fun `Element Call url with url extra param in fragment appPrompt and other gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=true&otherParam=maybe",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&otherParam=maybe&confineToRoom=true"
- )
- }
-
- @Test
- fun `Element Call url with url extra param confineToRoom gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM&confineToRoom=false",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
- )
- }
-
- @Test
- fun `Element Call url with url extra param in fragment confineToRoom gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=false",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&appPrompt=false"
- )
- }
-
- @Test
- fun `Element Call url with url extra param in fragment confineToRoom and more gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=false&otherParam=maybe",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&otherParam=maybe&appPrompt=false"
- )
- }
-
- @Test
- fun `Element Call url with url fragment gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#fragment",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?$EXTRA_PARAMS"
- )
- }
-
- @Test
- fun `Element Call url with url fragment with params gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe&$EXTRA_PARAMS"
- )
- }
-
- @Test
- fun `Element Call url with url fragment with other params gets url extracted`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe&$EXTRA_PARAMS"
- )
- }
-
- @Test
- fun `Element Call url with empty fragment`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
- )
- }
-
- @Test
- fun `Element Call url with empty fragment query`() {
- doTest(
- url = "$VALID_CALL_URL_WITH_PARAM#?",
- expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
- )
- }
-
- private fun doTest(url: String, expectedResult: String?) {
- // Test direct parsing
- assertThat(callIntentDataParser.parse(url)).isEqualTo(expectedResult)
-
- // Test embedded url, scheme 1
- val encodedUrl = URLEncoder.encode(url, "utf-8")
- val urlScheme1 = "element://call?url=$encodedUrl"
- assertThat(callIntentDataParser.parse(urlScheme1)).isEqualTo(expectedResult)
-
- // Test embedded url, scheme 2
- val urlScheme2 = "io.element.call:/?url=$encodedUrl"
- assertThat(callIntentDataParser.parse(urlScheme2)).isEqualTo(expectedResult)
- }
-
- companion object {
- const val VALID_CALL_URL_WITH_PARAM = "https://call.element.io/some-actual-call?with=parameters"
- const val EXTRA_PARAMS = "appPrompt=false&confineToRoom=true"
- }
-}
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt
index f9f6206ec7..3712904b03 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt
@@ -13,7 +13,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
import io.element.android.features.call.impl.notifications.aCallNotificationData
import io.element.android.features.call.impl.utils.ActiveCall
@@ -77,7 +77,7 @@ class DefaultActiveCallManagerTest {
assertThat(manager.activeCall.value).isEqualTo(
ActiveCall(
- callType = CallType.RoomCall(
+ callData = CallData(
sessionId = callNotificationData.sessionId,
roomId = callNotificationData.roomId,
isAudioCall = false,
@@ -104,7 +104,7 @@ class DefaultActiveCallManagerTest {
assertThat(manager.activeCall.value).isEqualTo(
ActiveCall(
- callType = CallType.RoomCall(
+ callData = CallData(
sessionId = callNotificationData.sessionId,
roomId = callNotificationData.roomId,
isAudioCall = true,
@@ -132,7 +132,7 @@ class DefaultActiveCallManagerTest {
manager.registerIncomingCall(aCallNotificationData(roomId = A_ROOM_ID_2))
assertThat(manager.activeCall.value).isEqualTo(activeCall)
- assertThat((manager.activeCall.value?.callType as? CallType.RoomCall)?.roomId).isNotEqualTo(A_ROOM_ID_2)
+ assertThat(manager.activeCall.value?.callData?.roomId).isNotEqualTo(A_ROOM_ID_2)
advanceTimeBy(1)
@@ -178,7 +178,7 @@ class DefaultActiveCallManagerTest {
}
@Test
- fun `hangUpCall - removes existing call if the CallType matches`() = runTest {
+ fun `hangUpCall - removes existing call if the CallData matches`() = runTest {
setupShadowPowerManager()
val notificationManagerCompat = mockk(relaxed = true)
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
@@ -188,7 +188,7 @@ class DefaultActiveCallManagerTest {
assertThat(manager.activeCall.value).isNotNull()
assertThat(manager.activeWakeLock?.isHeld).isTrue()
- manager.hangUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId, false))
+ manager.hangUpCall(CallData(notificationData.sessionId, notificationData.roomId, false))
assertThat(manager.activeCall.value).isNull()
assertThat(manager.activeWakeLock?.isHeld).isFalse()
@@ -215,7 +215,7 @@ class DefaultActiveCallManagerTest {
val notificationData = aCallNotificationData(roomId = A_ROOM_ID)
manager.registerIncomingCall(notificationData)
- manager.hangUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId, false))
+ manager.hangUpCall(CallData(notificationData.sessionId, notificationData.roomId, false))
coVerify {
room.declineCall(notificationEventId = notificationData.eventId)
@@ -242,7 +242,7 @@ class DefaultActiveCallManagerTest {
val notificationData = aCallNotificationData(roomId = A_ROOM_ID)
// Do not register the incoming call, so the manager doesn't know about it
manager.hangUpCall(
- callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId, false),
+ callData = CallData(notificationData.sessionId, notificationData.roomId, false),
notificationData = notificationData,
)
coVerify {
@@ -320,7 +320,7 @@ class DefaultActiveCallManagerTest {
}
@Test
- fun `hangUpCall - does nothing if the CallType doesn't match`() = runTest {
+ fun `hangUpCall - does nothing if the CallData doesn't match`() = runTest {
setupShadowPowerManager()
val notificationManagerCompat = mockk(relaxed = true)
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
@@ -329,7 +329,13 @@ class DefaultActiveCallManagerTest {
assertThat(manager.activeCall.value).isNotNull()
assertThat(manager.activeWakeLock?.isHeld).isTrue()
- manager.hangUpCall(CallType.ExternalUrl("https://example.com"))
+ manager.hangUpCall(
+ CallData(
+ sessionId = A_SESSION_ID,
+ roomId = A_ROOM_ID_2,
+ isAudioCall = true,
+ )
+ )
assertThat(manager.activeCall.value).isNotNull()
assertThat(manager.activeWakeLock?.isHeld).isTrue()
@@ -344,10 +350,10 @@ class DefaultActiveCallManagerTest {
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
assertThat(manager.activeCall.value).isNull()
- manager.joinedCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, true))
+ manager.joinedCall(CallData(A_SESSION_ID, A_ROOM_ID, true))
assertThat(manager.activeCall.value).isEqualTo(
ActiveCall(
- callType = CallType.RoomCall(
+ callData = CallData(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
isAudioCall = true,
@@ -450,7 +456,7 @@ class DefaultActiveCallManagerTest {
assertThat(manager.activeCall.value).isEqualTo(
ActiveCall(
- callType = CallType.RoomCall(
+ callData = CallData(
sessionId = callNotificationData.sessionId,
roomId = callNotificationData.roomId,
isAudioCall = false,
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt
index 2d0e126ab5..c2c38284a9 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt
@@ -8,7 +8,7 @@
package io.element.android.features.call.utils
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.impl.notifications.CallNotificationData
import io.element.android.features.call.impl.utils.ActiveCall
import io.element.android.features.call.impl.utils.ActiveCallManager
@@ -17,8 +17,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
class FakeActiveCallManager(
var registerIncomingCallResult: (CallNotificationData) -> Unit = {},
- var hangUpCallResult: (CallType, CallNotificationData?) -> Unit = { _, _ -> },
- var joinedCallResult: (CallType) -> Unit = {},
+ var hangUpCallResult: (CallData, CallNotificationData?) -> Unit = { _, _ -> },
+ var joinedCallResult: (CallData) -> Unit = {},
) : ActiveCallManager {
override val activeCall = MutableStateFlow(null)
@@ -26,12 +26,12 @@ class FakeActiveCallManager(
registerIncomingCallResult(notificationData)
}
- override suspend fun hangUpCall(callType: CallType, notificationData: CallNotificationData?) = simulateLongTask {
- hangUpCallResult(callType, notificationData)
+ override suspend fun hangUpCall(callData: CallData, notificationData: CallNotificationData?) = simulateLongTask {
+ hangUpCallResult(callData, notificationData)
}
- override suspend fun joinedCall(callType: CallType) = simulateLongTask {
- joinedCallResult(callType)
+ override suspend fun joinedCall(callData: CallData) = simulateLongTask {
+ joinedCallResult(callData)
}
fun setActiveCall(value: ActiveCall?) {
diff --git a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt
index fdf3ca566b..13b61feacb 100644
--- a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt
+++ b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt
@@ -8,16 +8,16 @@
package io.element.android.features.call.test
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.tests.testutils.lambda.lambdaError
class FakeElementCallEntryPoint(
- var startCallResult: (CallType) -> Unit = { lambdaError() },
+ var startCallResult: (CallData) -> Unit = { lambdaError() },
var handleIncomingCallResult: (
- CallType.RoomCall,
+ CallData,
EventId,
UserId,
String?,
@@ -27,12 +27,12 @@ class FakeElementCallEntryPoint(
String?,
) -> Unit = { _, _, _, _, _, _, _, _ -> lambdaError() }
) : ElementCallEntryPoint {
- override fun startCall(callType: CallType) {
- startCallResult(callType)
+ override fun startCall(callData: CallData) {
+ startCallResult(callData)
}
override suspend fun handleIncomingCall(
- callType: CallType.RoomCall,
+ callData: CallData,
eventId: EventId,
senderId: UserId,
roomName: String?,
@@ -44,7 +44,7 @@ class FakeElementCallEntryPoint(
textContent: String?,
) {
handleIncomingCallResult(
- callType,
+ callData,
eventId,
senderId,
roomName,
diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/SelectParentSpaceOptions.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/SelectParentSpaceOptions.kt
index 6b7b66b897..1480e57334 100644
--- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/SelectParentSpaceOptions.kt
+++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/SelectParentSpaceOptions.kt
@@ -95,7 +95,8 @@ internal fun SelectParentSpaceOptions(
sheetState.hide(coroutineScope) {
displaySelectSpaceBottomSheet = false
}
- }
+ },
+ scrollable = false,
) {
SelectParentSpaceBottomSheet(
spaces = spaces,
diff --git a/features/createroom/impl/src/main/res/values-da/translations.xml b/features/createroom/impl/src/main/res/values-da/translations.xml
index 66c4f08b70..ab72755dcf 100644
--- a/features/createroom/impl/src/main/res/values-da/translations.xml
+++ b/features/createroom/impl/src/main/res/values-da/translations.xml
@@ -19,7 +19,7 @@ Du kan ændre dette når som helst i rummets indstillinger."
"Anmod om at deltage"
"Kun inviterede brugere kan deltage."
"Privat"
- "Alle kan deltage i dette rum"
+ "Alle kan deltage."
"Offentlig"
"Alle i %1$s kan deltage."
"Standard"
diff --git a/features/createroom/impl/src/main/res/values-de/translations.xml b/features/createroom/impl/src/main/res/values-de/translations.xml
index 4408bf66a3..c3f42ca287 100644
--- a/features/createroom/impl/src/main/res/values-de/translations.xml
+++ b/features/createroom/impl/src/main/res/values-de/translations.xml
@@ -28,7 +28,8 @@ Du kannst dies jederzeit in den Einstellungen des Chats ändern."
"Adresse"
" Sichtbarkeit des Chats"
"(kein Space)"
- "Home"
+ "Nicht zu einem Space hinzufügen"
+ "Kein Space ausgewählt"
"Space hinzufügen"
"Thema (optional)"
"Beschreibung hinzufügen…"
diff --git a/features/createroom/impl/src/main/res/values-fa/translations.xml b/features/createroom/impl/src/main/res/values-fa/translations.xml
index 27542ccc19..821d55af67 100644
--- a/features/createroom/impl/src/main/res/values-fa/translations.xml
+++ b/features/createroom/impl/src/main/res/values-fa/translations.xml
@@ -3,7 +3,7 @@
"اتاق جدید"
"دعوت افراد"
"هنگام ایجاد اتاق خطایی رخ داد"
- "تنها افراد دعوت شده میتوانند به این اتاق دسترسی داشته باشند. همهٔ پیامها رمزنگاری سرتاسری شدهاند."
+ "تنها افراد دعوت شده میتوانند بپیوندند."
"هرکسی میتواند اتاق را بیابد.
میتوانید بعداً در تظیمات اتاق عوضش کنید."
"درخواست دعوت"
diff --git a/features/createroom/impl/src/main/res/values-ro/translations.xml b/features/createroom/impl/src/main/res/values-ro/translations.xml
index a46fd1a1c4..fd1854db40 100644
--- a/features/createroom/impl/src/main/res/values-ro/translations.xml
+++ b/features/createroom/impl/src/main/res/values-ro/translations.xml
@@ -3,14 +3,14 @@
"Cameră nouă"
"Invitați prieteni"
"A apărut o eroare la crearea camerei"
- "Doar persoanele invitate pot accesa această cameră. Toate mesajele sunt criptate end-to-end."
+ "Doar persoanele invitate se pot alătura."
"Oricine poate găsi această cameră.
Puteți modifica acest lucru oricând în setări."
"Oricine poate cere să se alăture camerei, dar un administrator sau un moderator va trebui să accepte cererea"
- "Cereți să vă alăturați"
+ "Permite solicitarea de alăturare"
"Oricine se poate alătura acestei camere"
"Pentru ca această cameră să fie vizibilă în directorul de camere publice, veți avea nevoie de o adresă de cameră."
- "Adresa camerei"
+ "Adresă"
"Vizibilitatea camerei"
"Subiect (opțional)"
diff --git a/features/createroom/impl/src/main/res/values-zh/translations.xml b/features/createroom/impl/src/main/res/values-zh/translations.xml
index 46d9654fbf..1ba1036634 100644
--- a/features/createroom/impl/src/main/res/values-zh/translations.xml
+++ b/features/createroom/impl/src/main/res/values-zh/translations.xml
@@ -1,36 +1,36 @@
- "新聊天室"
- "邀请朋友"
- "创建聊天室时出错"
+ "新房间"
+ "邀请人员"
+ "创建房间时出错"
"由于未知错误,空间创建失败。请稍后再试。"
"添加名称…"
- "新聊天室"
+ "新房间"
"新空间"
- "仅限受邀者加入。"
+ "仅限受邀人员加入。"
"私密"
- "任何人都能找到此聊天室。
-你可以随时在聊天室设置中更改。"
- "任何人都可以找到并加入"
+ "任何人都能找到此房间。
+你可以随时在房间设置中更改。"
+ "任何人都可以加入"
"公共"
- "任何人都可申请加入,但需由管理员或版主批准请求。"
- "请求加入"
- "%1$s 中的任何人都可加入,但其他人必须申请访问权限。"
+ "任何人都可申请加入,但需由管理员或协管员批准申请。"
+ "申请加入"
+ "%1$s 中的任何人都可以加入,但其他人必须申请访问。"
"申请加入"
- "仅限受邀者加入。"
+ "仅限受邀人员加入。"
"私密"
"任何人都可以加入。"
"公共"
- "%1$s 中的任何人可加入。"
+ "%1$s 中的任何人都可以加入。"
"标准"
"谁有权访问此房间"
- "要使该聊天室在公共目录中可见,您需要一个聊天室地址。"
+ "要使该房间在公共目录中可见,你需要一个地址。"
"地址"
"房间可见性"
"(无空间)"
- "请勿添加至空间"
+ "不要添加到空间"
"未选择空间"
- "添加至空间"
+ "添加到空间"
"主题(可选)"
"添加描述…"
diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt
index fcedcb2367..b9d7eb6e7d 100644
--- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt
+++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt
@@ -301,7 +301,9 @@ class ConfigureRoomPresenterTest {
roomName = 0,
roomAvatar = 0,
roomTopic = 0,
- spaceChild = 0
+ spaceChild = 0,
+ beacon = 0,
+ beaconInfo = 0,
),
users = persistentMapOf(),
)
diff --git a/features/deactivation/impl/src/main/res/values-bg/translations.xml b/features/deactivation/impl/src/main/res/values-bg/translations.xml
index 34ad5b4772..936e4726a5 100644
--- a/features/deactivation/impl/src/main/res/values-bg/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-bg/translations.xml
@@ -1,5 +1,4 @@
"Моля, потвърдете, че искате да деактивирате акаунта си. Това действие не може да бъде отменено."
- "Деактивиране на акаунта"
diff --git a/features/deactivation/impl/src/main/res/values-da/translations.xml b/features/deactivation/impl/src/main/res/values-da/translations.xml
index c6dcb1710a..f434547b8a 100644
--- a/features/deactivation/impl/src/main/res/values-da/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-da/translations.xml
@@ -1,14 +1,14 @@
- "Bekræft venligst, at du vil deaktivere din konto. Denne handling kan ikke fortrydes."
+ "Bekræft venligst, at du ønsker at slette din konto. Denne handling kan ikke fortrydes."
"Slet alle mine beskeder"
"Advarsel: Fremtidige brugere kan muligvis se ufuldstændige samtaler."
- "Deaktivering af din konto er %1$s, det vil:"
+ "Sletning af din konto er %1$s, det vil:"
"irreversibel"
"%1$s din konto (du kan ikke logge ind igen, og dit ID kan ikke genbruges)."
"Permanent deaktivere"
"Fjerne dig fra alle samtaler"
"Slette dine kontooplysninger fra vores identitetsserver."
"Dine beskeder vil stadig være synlige for registrerede brugere, men vil ikke være tilgængelige for nye eller uregistrerede brugere, hvis du vælger at slette dem."
- "Deaktiver konto"
+ "Slet konto"
diff --git a/features/deactivation/impl/src/main/res/values-de/translations.xml b/features/deactivation/impl/src/main/res/values-de/translations.xml
index 1aec7495a1..8430134d00 100644
--- a/features/deactivation/impl/src/main/res/values-de/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-de/translations.xml
@@ -1,14 +1,14 @@
- "Bitte bestätige, dass du dein Konto deaktivieren möchtest. Dies kann nicht rückgängig gemacht werden."
+ "Bitte bestätige, dass du dein Konto löschen möchtest. Diese Aktion kann nicht rückgängig gemacht werden."
"Lösche alle meine Nachrichten"
"Warnung: Künftigen Nutzern werden möglicherweise unvollständige Konversationen angezeigt."
- "Dein Konto zu deaktivieren ist %1$s. Folgendes wird passieren:"
+ "Das Löschen deines Kontos ist %1$s. Es wird:"
"irreversibel"
"%1$s dein Konto (du kannst dich nicht erneut anmelden und deine ID kann nicht wiederverwendet werden)."
"Dauerhaft deaktivieren"
"Du wirst aus allen Chats entfernt."
"Lösche deine Kontoinformationen von unserem Identitätsserver."
"Deine Nachrichten werden für bereits registrierte Nutzer weiterhin sichtbar sein. Für neue oder unregistrierte Nutzer sind sie nicht verfügbar, wenn du sie löschen solltest."
- "Nutzerkonto deaktivieren"
+ "Konto löschen"
diff --git a/features/deactivation/impl/src/main/res/values-es/translations.xml b/features/deactivation/impl/src/main/res/values-es/translations.xml
index cd0757ba3e..17ae73d6c8 100644
--- a/features/deactivation/impl/src/main/res/values-es/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-es/translations.xml
@@ -10,5 +10,4 @@
"Te eliminará de todas las salas de chat."
"Eliminará la información de tu cuenta de nuestro servidor de identidad."
"Tus mensajes seguirán siendo visibles para los usuarios registrados, pero no estarán disponibles para los usuarios nuevos o no registrados si decides eliminarlos."
- "Desactivar cuenta"
diff --git a/features/deactivation/impl/src/main/res/values-fr/translations.xml b/features/deactivation/impl/src/main/res/values-fr/translations.xml
index 675ac1e1e0..cf69cb3275 100644
--- a/features/deactivation/impl/src/main/res/values-fr/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-fr/translations.xml
@@ -1,14 +1,14 @@
- "Veuillez confirmer que vous souhaitez désactiver votre compte. Cette action ne peut pas être annulée."
+ "Veuillez confirmer que vous souhaitez supprimer votre compte. Cette action ne peut pas être annulée."
"Supprimer tous mes messages"
"Attention : les futurs utilisateurs pourraient voir des conversations incomplètes."
- "La désactivation de votre compte est %1$s, cela va :"
+ "La suppression de votre compte est %1$s, cela va :"
"irréversible"
"%1$s votre compte (vous ne pourrez plus vous reconnecter et votre identifiant ne pourra pas être réutilisé)."
"Désactiver définitivement"
"Vous retirer de tous les salons et toutes les discussions."
"Supprimer les informations de votre compte du serveur d’identité."
"Rendre vos messages invisibles aux futurs membres des salons si vous choisissez de les supprimer. Vos messages seront toujours visibles pour les utilisateurs qui les ont déjà récupérés."
- "Désactiver le compte"
+ "Supprimer le compte"
diff --git a/features/deactivation/impl/src/main/res/values-hr/translations.xml b/features/deactivation/impl/src/main/res/values-hr/translations.xml
index 04148fdc48..1d8a02a08c 100644
--- a/features/deactivation/impl/src/main/res/values-hr/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-hr/translations.xml
@@ -10,5 +10,5 @@
"Ukloniti vas iz svih soba za razgovore."
"Izbrisati podatke o vašem računu s našeg poslužitelja identiteta."
"Vaše će poruke i dalje biti vidljive registriranim korisnicima, ali neće biti dostupne novim ili neregistriranim korisnicima ako ih odlučite izbrisati."
- "Deaktiviraj račun"
+ "Izbriši račun"
diff --git a/features/deactivation/impl/src/main/res/values-hu/translations.xml b/features/deactivation/impl/src/main/res/values-hu/translations.xml
index 3d3722b8ef..2c3f51ed7a 100644
--- a/features/deactivation/impl/src/main/res/values-hu/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-hu/translations.xml
@@ -1,14 +1,14 @@
- "Erősítse meg, hogy deaktiválja a fiókját. Ez a művelet nem vonható vissza."
+ "Erősítse meg a fiókja törlését. Ez a művelet nem vonható vissza."
"Összes saját üzenet törlése"
"Figyelmeztetés: A jövőbeli felhasználók hiányos beszélgetéseket láthatnak."
- "A fiók deaktiválása %1$s, a következőket okozza:"
+ "Fiókjának törlése: %1$s, ez a következőket eredményezi:"
"visszafordíthatatlan"
"%1$s a fiókját (nem fog tudni újra bejelentkezni, és az azonosítója nem használható újra)."
"Véglegesen letiltja"
"Eltávolításra kerül az összes csevegőszobából."
"Törlésre kerülnek a fiókadatai az azonosítási kiszolgálónkról."
"Üzenetei továbbra is láthatóak maradnak a regisztrált felhasználók számára, de nem lesznek elérhetőek az új vagy nem regisztrált felhasználók számára, ha úgy dönt, hogy törli őket."
- "Fiók deaktiválása"
+ "Fiók törlése"
diff --git a/features/deactivation/impl/src/main/res/values-it/translations.xml b/features/deactivation/impl/src/main/res/values-it/translations.xml
index 3fbc9d536b..7cd484d0e4 100644
--- a/features/deactivation/impl/src/main/res/values-it/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-it/translations.xml
@@ -10,5 +10,5 @@
"Ti rimuove da tutte le stanze di chat."
"Elimina le informazioni del tuo account dal nostro server di identità."
"I tuoi messaggi saranno ancora visibili agli utenti registrati, ma non saranno disponibili per gli utenti nuovi o non registrati se decidi di eliminarli."
- "Disattiva account"
+ "Disattivazione dell\'account"
diff --git a/features/deactivation/impl/src/main/res/values-ja/translations.xml b/features/deactivation/impl/src/main/res/values-ja/translations.xml
index 873b1308ec..53893a594f 100644
--- a/features/deactivation/impl/src/main/res/values-ja/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-ja/translations.xml
@@ -1,14 +1,14 @@
- "アカウントを無効化することを再度確認します。この操作は元に戻せません。"
+ "アカウントを削除しようとしていることを確認しています。この操作は元に戻せません。"
"メッセージをすべて削除"
"注意: 新しいユーザーには断片的な会話が表示されます"
- "アカウントを無効化することは %1$s であり、次の変化が生じます:"
+ "アカウントを削除することは %1$s であり、次の変化が生じます:"
"不可逆"
"アカウントを %1$s (再度ログイン不可, 同一のIDを再利用不可)"
"恒久的に無効化する"
"すべてのチャットルームから退出します。"
"アカウント提供元サーバーからアカウント情報を削除します。"
"あなたの会話は、既存ユーザーには引き続き表示されますが、新規ユーザーには表示されなくなります。"
- "アカウントを無効化"
+ "アカウントを削除"
diff --git a/features/deactivation/impl/src/main/res/values-ko/translations.xml b/features/deactivation/impl/src/main/res/values-ko/translations.xml
index 6b7953a4a5..42dd0aa0e2 100644
--- a/features/deactivation/impl/src/main/res/values-ko/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-ko/translations.xml
@@ -10,5 +10,4 @@
"모든 채팅방에서 자신을 제거하세요."
"당사의 신원 서버에서 귀하의 계정 정보를 삭제하세요."
"메시지는 등록된 사용자에게는 계속 표시되지만, 삭제하면 신규 또는 미등록 사용자는 볼 수 없게 됩니다."
- "계정 비활성화"
diff --git a/features/deactivation/impl/src/main/res/values-pt-rBR/translations.xml b/features/deactivation/impl/src/main/res/values-pt-rBR/translations.xml
index 7000a65d47..a986b18a7c 100644
--- a/features/deactivation/impl/src/main/res/values-pt-rBR/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-pt-rBR/translations.xml
@@ -10,5 +10,4 @@
"Te remover de todas as salas de conversa."
"Apague as informações da sua conta do nosso servidor de identidade."
"Suas mensagens ainda estarão visíveis para os usuários registrados, mas não estarão disponíveis para usuários novos ou não registrados se você optar por apagá-las."
- "Desativar conta"
diff --git a/features/deactivation/impl/src/main/res/values-uk/translations.xml b/features/deactivation/impl/src/main/res/values-uk/translations.xml
index 04b32df8b2..62cf66cec1 100644
--- a/features/deactivation/impl/src/main/res/values-uk/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-uk/translations.xml
@@ -10,5 +10,5 @@
"Видалити вас з усіх чатів."
"Видаліть інформацію свого облікового запису з нашого сервера ідентифікації."
"Ваші повідомлення залишатимуться видимими для зареєстрованих користувачів, але недоступними для нових або незареєстрованих користувачів, якщо ви вирішите їх видалити."
- "Деактивувати обліковий запис"
+ "Відключити обліковий запис"
diff --git a/features/deactivation/impl/src/main/res/values-ur/translations.xml b/features/deactivation/impl/src/main/res/values-ur/translations.xml
index 297b29c519..3cad49aeb3 100644
--- a/features/deactivation/impl/src/main/res/values-ur/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-ur/translations.xml
@@ -10,5 +10,4 @@
"آپ کو تمام چیت رومز سے ہٹا دے گا۔"
"ہمارے شناختی سرور سے اپنے اکاؤنٹ کی معلومات کو حذف کریں۔"
"آپ کے پیغامات اب بھی رجسٹرڈ صارفین کو نظر آئیں گے لیکن اگر آپ انہیں حذف کرنے کا انتخاب کرتے ہیں تو نئے یا غیر رجسٹرڈ صارفین کے لیے دستیاب نہیں ہوں گے۔"
- "اکاؤنٹ کو غیر فعال کریں"
diff --git a/features/deactivation/impl/src/main/res/values-uz/translations.xml b/features/deactivation/impl/src/main/res/values-uz/translations.xml
index 19a70bb149..d357f38186 100644
--- a/features/deactivation/impl/src/main/res/values-uz/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-uz/translations.xml
@@ -10,5 +10,4 @@
"Sizni barcha chat xonalaridan olib tashlash."
"Hisobingiz haqidagi axborotni identifikatsiya serverimizdan o‘chirib tashlang."
"Xabarlaringiz ro‘yxatdan o‘tgan foydalanuvchilarga ko‘rinadi, lekin ularni o‘chirishni tanlasangiz, yangi yoki ro‘yxatdan o‘tmagan foydalanuvchilarga ko‘rinmaydi."
- "Hisobni faolsizlantirish"
diff --git a/features/deactivation/impl/src/main/res/values-vi/translations.xml b/features/deactivation/impl/src/main/res/values-vi/translations.xml
index b61167ff80..f3b48163ed 100644
--- a/features/deactivation/impl/src/main/res/values-vi/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-vi/translations.xml
@@ -10,5 +10,4 @@
"Loại bỏ bạn khỏi tất cả các phòng chat."
"Xóa thông tin tài khoản của bạn khỏi máy chủ nhận dạng của chúng tôi."
"Tin nhắn của bạn vẫn sẽ hiển thị cho người dùng đã đăng ký nhưng sẽ không hiển thị cho người dùng mới hoặc chưa đăng ký nếu bạn chọn xóa chúng."
- "Vô hiệu hóa tài khoản"
diff --git a/features/deactivation/impl/src/main/res/values-zh/translations.xml b/features/deactivation/impl/src/main/res/values-zh/translations.xml
index ca24375d66..3916921652 100644
--- a/features/deactivation/impl/src/main/res/values-zh/translations.xml
+++ b/features/deactivation/impl/src/main/res/values-zh/translations.xml
@@ -1,14 +1,14 @@
- "请确认您要停用您的账户。此操作无法撤消。"
+ "请确认要删除的账户。此操作无法撤消。"
"删除我的所有消息"
"警告:未来的用户可能会看到不完整的对话。"
- "停用您的帐户是%1$s,它将:"
- "不可逆转的"
- "%1$s您的账户(您无法登录回来,并且您的ID无法重复使用)。"
+ "正在删除的账户为 %1$s,它将:"
+ "不可逆"
+ "你的账户 %1$s(将无法再登录,并且 ID 无法重复使用)。"
"永久禁用"
- "将您从所有聊天房间中移除。"
- "从我们的身份服务器中删除您的账户信息。"
- "注册用户仍可看到您的消息,但如果您选择删除它们,新用户或未注册用户将无法看到您的消息。"
- "停用账户"
+ "将你从所有聊天房间中移除。"
+ "从我们的身份服务器中删除你的账户信息。"
+ "注册用户仍可看到你的消息,但如果选择删除它们,新用户或未注册用户将无法看到你的消息。"
+ "删除账户"
diff --git a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt
index 26c942da1f..c672fd666b 100644
--- a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt
+++ b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt
@@ -6,13 +6,16 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.logout.impl
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.deactivation.impl.R
import io.element.android.libraries.architecture.AsyncAction
@@ -26,33 +29,29 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressTag
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class AccountDeactivationViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back invokes the expected callback`() {
+ fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setAccountDeactivationView(
+ setAccountDeactivationView(
state = anAccountDeactivationState(eventSink = eventsRecorder),
onBackClick = it,
)
- rule.pressBack()
+ pressBack()
}
}
@Config(qualifiers = "h1024dp")
@Test
- fun `clicking on Deactivate emits the expected Event`() {
+ fun `clicking on Deactivate emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setAccountDeactivationView(
+ setAccountDeactivationView(
state = anAccountDeactivationState(
deactivateFormState = aDeactivateFormState(
password = A_PASSWORD,
@@ -60,14 +59,14 @@ class AccountDeactivationViewTest {
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_delete)
+ clickOn(CommonStrings.action_delete)
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false))
}
@Test
- fun `clicking on Deactivate on the confirmation dialog emits the expected Event`() {
+ fun `clicking on Deactivate on the confirmation dialog emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setAccountDeactivationView(
+ setAccountDeactivationView(
state = anAccountDeactivationState(
deactivateFormState = aDeactivateFormState(
password = A_PASSWORD,
@@ -76,14 +75,14 @@ class AccountDeactivationViewTest {
eventSink = eventsRecorder,
),
)
- rule.pressTag(TestTags.dialogPositive.value)
+ pressTag(TestTags.dialogPositive.value)
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false))
}
@Test
- fun `clicking on retry on the confirmation dialog emits the expected Event`() {
+ fun `clicking on retry on the confirmation dialog emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setAccountDeactivationView(
+ setAccountDeactivationView(
state = anAccountDeactivationState(
deactivateFormState = aDeactivateFormState(
password = A_PASSWORD,
@@ -92,26 +91,26 @@ class AccountDeactivationViewTest {
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_retry)
+ clickOn(CommonStrings.action_retry)
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(true))
}
@Test
- fun `switching on the erase all switch emits the expected Event`() {
+ fun `switching on the erase all switch emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setAccountDeactivationView(
+ setAccountDeactivationView(
state = anAccountDeactivationState(
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_deactivate_account_delete_all_messages)
+ clickOn(R.string.screen_deactivate_account_delete_all_messages)
eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(true))
}
@Test
- fun `switching off the erase all switch emits the expected Event`() {
+ fun `switching off the erase all switch emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setAccountDeactivationView(
+ setAccountDeactivationView(
state = anAccountDeactivationState(
deactivateFormState = aDeactivateFormState(
eraseData = true,
@@ -119,15 +118,15 @@ class AccountDeactivationViewTest {
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_deactivate_account_delete_all_messages)
+ clickOn(R.string.screen_deactivate_account_delete_all_messages)
eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(false))
}
@Config(qualifiers = "h1024dp")
@Test
- fun `typing text in the password field emits the expected Event`() {
+ fun `typing text in the password field emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setAccountDeactivationView(
+ setAccountDeactivationView(
state = anAccountDeactivationState(
deactivateFormState = aDeactivateFormState(
password = A_PASSWORD,
@@ -135,12 +134,12 @@ class AccountDeactivationViewTest {
eventSink = eventsRecorder,
),
)
- rule.onNodeWithTag(TestTags.loginPassword.value).performTextInput("A")
+ onNodeWithTag(TestTags.loginPassword.value).performTextInput("A")
eventsRecorder.assertSingle(AccountDeactivationEvents.SetPassword("A$A_PASSWORD"))
}
}
-private fun AndroidComposeTestRule.setAccountDeactivationView(
+private fun AndroidComposeUiTest.setAccountDeactivationView(
state: AccountDeactivationState,
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
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 65fe3fe087..92d8b9b646 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
@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.Flow
interface EnterpriseService {
val isEnterpriseBuild: Boolean
suspend fun isEnterpriseUser(sessionId: SessionId): Boolean
+ suspend fun tweakMasUrl(url: String, homeserver: String): String
fun defaultHomeserverList(): List
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt
index 6bd6c78de5..f87dc743e8 100644
--- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt
+++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt
@@ -10,6 +10,7 @@ package io.element.android.features.enterprise.api
interface SessionEnterpriseService {
suspend fun isElementCallAvailable(): Boolean
+ suspend fun tweakMasUrl(url: String): String
suspend fun init()
}
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 932d082fd9..6e3ed5d3cc 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
@@ -23,7 +23,7 @@ class DefaultEnterpriseService : EnterpriseService {
override val isEnterpriseBuild = false
override suspend fun isEnterpriseUser(sessionId: SessionId) = false
-
+ override suspend fun tweakMasUrl(url: String, homeserver: String) = url
override fun defaultHomeserverList(): List = emptyList()
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt
index 3441063a8a..9aafcd343c 100644
--- a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt
+++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt
@@ -15,5 +15,6 @@ import io.element.android.libraries.di.SessionScope
@ContributesBinding(SessionScope::class)
class DefaultSessionEnterpriseService : SessionEnterpriseService {
override suspend fun init() = Unit
+ override suspend fun tweakMasUrl(url: String): String = url
override suspend fun isElementCallAvailable(): Boolean = true
}
diff --git a/features/enterprise/test/build.gradle.kts b/features/enterprise/test/build.gradle.kts
index 542e73717a..c37fc53de3 100644
--- a/features/enterprise/test/build.gradle.kts
+++ b/features/enterprise/test/build.gradle.kts
@@ -15,6 +15,7 @@ android {
dependencies {
api(projects.features.enterprise.api)
+ implementation(projects.libraries.architecture)
implementation(projects.libraries.compound)
implementation(projects.libraries.matrix.api)
implementation(projects.tests.testutils)
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 3c17a4de7c..805c75be6a 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
@@ -30,6 +30,7 @@ class FakeEnterpriseService(
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
private val getNoisyNotificationChannelIdResult: (SessionId?) -> String? = { lambdaError() },
+ private val tweakMasUrlResult: (String, String) -> String = { _, _ -> lambdaError() },
) : EnterpriseService {
private val brandColorState = MutableStateFlow(initialBrandColor)
private val semanticColorsState = MutableStateFlow(initialSemanticColors)
@@ -38,6 +39,10 @@ class FakeEnterpriseService(
isEnterpriseUserResult(sessionId)
}
+ override suspend fun tweakMasUrl(url: String, homeserver: String): String = simulateLongTask {
+ tweakMasUrlResult(url, homeserver)
+ }
+
override fun defaultHomeserverList(): List {
return defaultHomeserverListResult()
}
diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt
index 3914c60155..0bcad13033 100644
--- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt
+++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt
@@ -14,10 +14,15 @@ import io.element.android.tests.testutils.simulateLongTask
class FakeSessionEnterpriseService(
private val isElementCallAvailableResult: () -> Boolean = { lambdaError() },
+ private val tweakMasUrlResult: (String) -> String = { lambdaError() },
) : SessionEnterpriseService {
override suspend fun init() {
}
+ override suspend fun tweakMasUrl(url: String): String = simulateLongTask {
+ tweakMasUrlResult(url)
+ }
+
override suspend fun isElementCallAvailable(): Boolean = simulateLongTask {
isElementCallAvailableResult()
}
diff --git a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt
index f1e9bd8fc6..57a9f65099 100644
--- a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt
+++ b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.forward.impl
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId
@@ -21,34 +24,30 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressTag
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ForwardMessagesViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `cancel error emits the expected event`() {
+ fun `cancel error emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setForwardMessagesView(
+ setForwardMessagesView(
aForwardMessagesState(
forwardAction = AsyncAction.Failure(AN_EXCEPTION),
eventSink = eventsRecorder
),
)
- rule.pressTag(TestTags.dialogPositive.value)
+ pressTag(TestTags.dialogPositive.value)
eventsRecorder.assertSingle(ForwardMessagesEvents.ClearError)
}
@Test
- fun `success invokes onForwardSuccess`() {
+ fun `success invokes onForwardSuccess`() = runAndroidComposeUiTest {
val data = listOf(A_ROOM_ID)
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnceWithParam?>(data) { callback ->
- rule.setForwardMessagesView(
+ setForwardMessagesView(
aForwardMessagesState(
forwardAction = AsyncAction.Success(data),
eventSink = eventsRecorder
@@ -59,7 +58,7 @@ class ForwardMessagesViewTest {
}
}
-private fun AndroidComposeTestRule.setForwardMessagesView(
+private fun AndroidComposeUiTest.setForwardMessagesView(
state: ForwardMessagesState,
onForwardSuccess: (List) -> Unit = EnsureNeverCalledWithParam(),
) {
diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt
index 0ca25c9455..1bfa10daf2 100644
--- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt
+++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt
@@ -19,6 +19,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
@@ -90,7 +93,11 @@ fun ChooseSelfVerificationModeView(
Text(
modifier = Modifier
.clickable(onClick = onLearnMore)
- .padding(vertical = 4.dp, horizontal = 16.dp),
+ .padding(vertical = 4.dp, horizontal = 16.dp)
+ .semantics {
+ // Note: there is no Role.Link, so we use Role.Button for better accessibility support
+ role = Role.Button
+ },
text = stringResource(CommonStrings.action_learn_more),
style = ElementTheme.typography.fontBodyLgMedium
)
diff --git a/features/ftue/impl/src/main/res/values-de/translations.xml b/features/ftue/impl/src/main/res/values-de/translations.xml
index 241f73516e..0e83b29e35 100644
--- a/features/ftue/impl/src/main/res/values-de/translations.xml
+++ b/features/ftue/impl/src/main/res/values-de/translations.xml
@@ -3,7 +3,7 @@
"Bestätigung unmöglich?"
"Erstelle einen neuen Wiederherstellungsschlüssel"
"Wähle eine Verifizierungsmethode, um den sicheren Nachrichtenversand einzurichten."
- "Bestätige deine Identität"
+ "Bestätige deine digitale Identität"
"Ein anderes Gerät verwenden"
"Wiederherstellungsschlüssel verwenden"
"Du kannst jetzt verschlüsselte Nachrichten lesen und versenden. Dein Chatpartner vertraut nun diesem Gerät."
diff --git a/features/ftue/impl/src/main/res/values-ja/translations.xml b/features/ftue/impl/src/main/res/values-ja/translations.xml
index 68b69079ef..9a87a3dcfa 100644
--- a/features/ftue/impl/src/main/res/values-ja/translations.xml
+++ b/features/ftue/impl/src/main/res/values-ja/translations.xml
@@ -11,5 +11,5 @@
"他の端末を使用"
"一方の端末を待機中…"
"設定は後で変更することができます。"
- "メッセージを見逃さないため通知を許可"
+ "メッセージを見逃さないために通知を許可しましょう"
diff --git a/features/ftue/impl/src/main/res/values-pt/translations.xml b/features/ftue/impl/src/main/res/values-pt/translations.xml
index 5b6729f04e..34f39c7bdb 100644
--- a/features/ftue/impl/src/main/res/values-pt/translations.xml
+++ b/features/ftue/impl/src/main/res/values-pt/translations.xml
@@ -3,7 +3,7 @@
"Não é possível confirmar?"
"Criar uma nova chave de recuperação"
"Verifica este dispositivo para configurar o envio seguro de mensagens."
- "Confirma que és tu"
+ "Confirma a tua identidade digital"
"Utilizar outro dispositivo"
"Utilizar chave de recuperação"
"Agora podes ler ou enviar mensagens de forma segura, e qualquer pessoa com quem converses também pode confiar neste dispositivo."
diff --git a/features/ftue/impl/src/main/res/values-ro/translations.xml b/features/ftue/impl/src/main/res/values-ro/translations.xml
index abf72140e8..85b151faa8 100644
--- a/features/ftue/impl/src/main/res/values-ro/translations.xml
+++ b/features/ftue/impl/src/main/res/values-ro/translations.xml
@@ -2,8 +2,8 @@
"Nu puteți confirma?"
"Creați o nouă cheie de recuperare"
- "Verificați acest dispozitiv pentru a configura mesagerie securizată."
- "Confirmați că sunteți dumneavoastră"
+ "Alegeți cum doriți să vă verificați pentru a configura mesageria securizată."
+ "Confirmați-vă identitatea digitală"
"Utilizați un alt dispozitiv"
"Utilizați cheia de recuperare"
"Acum puteți citi sau trimite mesaje în siguranță, iar oricine cu care conversați poate avea încredere în acest dispozitiv."
diff --git a/features/ftue/impl/src/main/res/values-zh/translations.xml b/features/ftue/impl/src/main/res/values-zh/translations.xml
index 68a48831e0..f9171c9879 100644
--- a/features/ftue/impl/src/main/res/values-zh/translations.xml
+++ b/features/ftue/impl/src/main/res/values-zh/translations.xml
@@ -3,13 +3,13 @@
"无法确认?"
"创建新的恢复密钥"
"选择验证方式以设置安全的消息传输。"
- "确认您的数字身份"
- "使用其他设备"
+ "确认你的数字身份"
+ "使用其它设备"
"使用恢复密钥"
- "现在,您可以安全地阅读或发送消息,与您聊天的人也会信任此设备。"
+ "现在你可以安全地读取或发送消息,并且与你聊天的任何人也可以信任此设备。"
"设备已验证"
- "使用其他设备"
- "正在等待其他设备……"
- "您可以稍后更改设置。"
+ "使用其它设备"
+ "正在等待其它设备…"
+ "你可以稍后更改设置。"
"允许通知,绝不错过任何消息"
diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt
index 521bf91b37..6e74f58f66 100644
--- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt
+++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.ftue.impl.sessionverification.choosemode
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.ftue.impl.R
import io.element.android.libraries.architecture.AsyncData
@@ -18,65 +21,61 @@ import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class ChooseSessionVerificationModeViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Config(qualifiers = "h1024dp")
@Test
- fun `clicking on learn more invokes the expected callback`() {
+ fun `clicking on learn more invokes the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setChooseSelfVerificationModeView(
+ setChooseSelfVerificationModeView(
aChooseSelfVerificationModeState(),
onLearnMoreClick = callback,
)
- rule.clickOn(CommonStrings.action_learn_more)
+ clickOn(CommonStrings.action_learn_more)
}
}
@Config(qualifiers = "h1024dp")
@Test
- fun `clicking on use another device calls the callback`() {
+ fun `clicking on use another device calls the callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setChooseSelfVerificationModeView(
+ setChooseSelfVerificationModeView(
aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseAnotherDevice = true))),
onUseAnotherDevice = callback,
)
- rule.clickOn(R.string.screen_identity_use_another_device)
+ clickOn(R.string.screen_identity_use_another_device)
}
}
@Config(qualifiers = "h1024dp")
@Test
- fun `clicking on enter recovery key calls the callback`() {
+ fun `clicking on enter recovery key calls the callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setChooseSelfVerificationModeView(
+ setChooseSelfVerificationModeView(
aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseRecoveryKey = true))),
onEnterRecoveryKey = callback,
)
- rule.clickOn(R.string.screen_identity_confirmation_use_recovery_key)
+ clickOn(R.string.screen_identity_confirmation_use_recovery_key)
}
}
@Config(qualifiers = "h1024dp")
@Test
- fun `clicking on cannot confirm calls the reset keys callback`() {
+ fun `clicking on cannot confirm calls the reset keys callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setChooseSelfVerificationModeView(
+ setChooseSelfVerificationModeView(
aChooseSelfVerificationModeState(),
onResetKey = callback,
)
- rule.clickOn(R.string.screen_identity_confirmation_cannot_confirm)
+ clickOn(R.string.screen_identity_confirmation_cannot_confirm)
}
}
- private fun AndroidComposeTestRule.setChooseSelfVerificationModeView(
+ private fun AndroidComposeUiTest.setChooseSelfVerificationModeView(
state: ChooseSelfVerificationModeState,
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
onUseAnotherDevice: () -> Unit = EnsureNeverCalled(),
diff --git a/features/home/impl/build.gradle.kts b/features/home/impl/build.gradle.kts
index b36ee6aed2..0635da39a5 100644
--- a/features/home/impl/build.gradle.kts
+++ b/features/home/impl/build.gradle.kts
@@ -46,6 +46,7 @@ dependencies {
implementation(projects.libraries.permissions.noop)
implementation(projects.libraries.preferences.api)
implementation(projects.libraries.push.api)
+ implementation(projects.libraries.sessionStorage.api)
implementation(projects.features.announcement.api)
implementation(projects.features.invite.api)
implementation(projects.features.networkmonitor.api)
diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt
index 81e7969080..00f285e3f4 100644
--- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt
+++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt
@@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import com.bumble.appyx.core.lifecycle.subscribe
@@ -171,6 +172,7 @@ class HomeFlowNode(
if (loadingJoinedRoomJob.value.isLoading()) {
DelayedVisibility(duration = 400.milliseconds) {
ProgressDialog(
+ properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true),
onDismissRequest = {
loadingJoinedRoomJob.value.dataOrNull()?.cancel()
loadingJoinedRoomJob.value = AsyncData.Uninitialized
diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt
index ff0fc00496..5f55beaf60 100644
--- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt
+++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt
@@ -57,6 +57,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGradient
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
@@ -65,8 +66,8 @@ import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.SessionId
-import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
+import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.testtags.TestTags
@@ -237,6 +238,7 @@ private fun SpaceFilterButton(
else -> Unit
}
}
+
val isSelected = spaceFiltersState is SpaceFiltersState.Selected
IconButton(
onClick = ::onClick,
@@ -320,7 +322,15 @@ private fun AccountIcon(
Avatar(
avatarData = avatarData,
avatarType = AvatarType.User,
- contentDescription = if (isCurrentAccount) stringResource(CommonStrings.common_settings) else null,
+ contentDescription = if (isCurrentAccount) {
+ if (showAvatarIndicator) {
+ stringResource(CommonStrings.a11y_settings_with_required_action)
+ } else {
+ stringResource(CommonStrings.common_settings)
+ }
+ } else {
+ null
+ },
)
if (showAvatarIndicator) {
RedIndicatorAtom(
@@ -337,7 +347,7 @@ private fun AccountIcon(
internal fun HomeTopBarPreview() = ElementPreview {
HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Chats,
- currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
+ currentUserAndNeighbors = persistentListOf(aMatrixUser(id = "@id:domain", displayName = USER_NAME_ALICE)),
showAvatarIndicator = false,
areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
@@ -358,7 +368,7 @@ internal fun HomeTopBarPreview() = ElementPreview {
internal fun HomeTopBarSpaceFiltersSelectedPreview() = ElementPreview {
HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Chats,
- currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
+ currentUserAndNeighbors = persistentListOf(aMatrixUser(id = "@id:domain", displayName = USER_NAME_ALICE)),
showAvatarIndicator = false,
areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
@@ -379,7 +389,7 @@ internal fun HomeTopBarSpaceFiltersSelectedPreview() = ElementPreview {
internal fun HomeTopBarSpacesPreview() = ElementPreview {
HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Spaces,
- currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
+ currentUserAndNeighbors = persistentListOf(aMatrixUser(id = "@id:domain", displayName = USER_NAME_ALICE)),
showAvatarIndicator = false,
areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
@@ -400,7 +410,7 @@ internal fun HomeTopBarSpacesPreview() = ElementPreview {
internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Chats,
- currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
+ currentUserAndNeighbors = persistentListOf(aMatrixUser(id = "@id:domain", displayName = USER_NAME_ALICE)),
showAvatarIndicator = true,
areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt
index f541417104..72ecb5046d 100644
--- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt
+++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt
@@ -29,6 +29,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
@@ -222,20 +223,17 @@ private fun NameAndTimestampRow(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = spacedBy(16.dp)
) {
- Row(
- modifier = Modifier.weight(1f),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- // Name
- Text(
- style = ElementTheme.typography.fontBodyLgMedium,
- text = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name),
- fontStyle = FontStyle.Italic.takeIf { name == null },
- color = ElementTheme.colors.roomListRoomName,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- }
+ Text(
+ modifier = Modifier
+ .weight(1f)
+ .clipToBounds(),
+ style = ElementTheme.typography.fontBodyLgMedium,
+ text = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name),
+ fontStyle = FontStyle.Italic.takeIf { name == null },
+ color = ElementTheme.colors.roomListRoomName,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
// Timestamp
Text(
text = timestamp ?: "",
@@ -262,12 +260,12 @@ private fun InviteSubtitle(
}
if (subtitle != null) {
Text(
+ modifier = modifier.clipToBounds(),
text = subtitle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.roomListRoomMessage,
- modifier = modifier,
)
}
}
@@ -326,7 +324,9 @@ private fun MessagePreviewAndIndicatorRow(
val messagePreview = room.latestEvent.content()
val annotatedMessagePreview = messagePreview as? AnnotatedString ?: AnnotatedString(text = messagePreview.orEmpty().toString())
Text(
- modifier = Modifier.weight(1f),
+ modifier = Modifier
+ .weight(1f)
+ .clipToBounds(),
text = annotatedMessagePreview,
color = ElementTheme.colors.roomListRoomMessage,
style = ElementTheme.typography.fontBodyMdRegular,
@@ -381,7 +381,9 @@ private fun InviteNameAndIndicatorRow(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
- modifier = Modifier.weight(1f),
+ modifier = Modifier
+ .weight(1f)
+ .clipToBounds(),
style = ElementTheme.typography.fontBodyLgMedium,
text = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name),
fontStyle = FontStyle.Italic.takeIf { name == null },
diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryProvider.kt
index eefb2d6484..09e6c2e6c9 100644
--- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryProvider.kt
+++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryProvider.kt
@@ -11,6 +11,10 @@ package io.element.android.features.home.impl.model
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
+import io.element.android.libraries.designsystem.preview.LAST_MESSAGE
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@@ -85,16 +89,16 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider Unit,
) {
Column(
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier
+ .fillMaxWidth()
+ .verticalScroll(rememberScrollState())
) {
ListItem(
headlineContent = {
@@ -212,23 +217,16 @@ private fun RoomListModalBottomSheetContent(
}
}
-// TODO This component should be seen in [RoomListView] @Preview but it doesn't show up.
-// see: https://issuetracker.google.com/issues/283843380
-// Remove this preview when the issue is fixed.
@PreviewsDayNight
@Composable
-internal fun RoomListModalBottomSheetContentPreview(
+internal fun RoomListContextMenuPreview(
@PreviewParameter(RoomListStateContextMenuShownProvider::class) contextMenu: RoomListState.ContextMenu.Shown
) = ElementPreview {
- RoomListModalBottomSheetContent(
+ RoomListContextMenu(
contextMenu = contextMenu,
canReportRoom = true,
- onRoomMarkReadClick = {},
- onRoomMarkUnreadClick = {},
onRoomSettingsClick = {},
- onLeaveRoomClick = {},
- onFavoriteChange = {},
- onClearCacheRoomClick = {},
onReportRoomClick = {},
+ eventSink = {},
)
}
diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenu.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenu.kt
index 523e677a57..0a7a29ebc2 100644
--- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenu.kt
+++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenu.kt
@@ -13,16 +13,21 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
+import io.element.android.appconfig.ProtectionConfig
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.home.impl.R
import io.element.android.features.home.impl.model.RoomListRoomSummary
+import io.element.android.libraries.core.extensions.toSafeLength
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
@@ -42,9 +47,14 @@ fun RoomListDeclineInviteMenu(
) {
ModalBottomSheet(
onDismissRequest = { eventSink(RoomListEvent.HideDeclineInviteMenu) },
+ scrollable = false,
) {
RoomListDeclineInviteMenuContent(
- roomName = menu.roomSummary.name ?: menu.roomSummary.roomId.value,
+ roomName = menu.roomSummary.name?.toSafeLength(
+ maxLength = ProtectionConfig.MAX_ROOM_NAME_LENGTH,
+ ellipsize = true,
+ )
+ ?: menu.roomSummary.roomId.value,
onDeclineClick = {
eventSink(RoomListEvent.HideDeclineInviteMenu)
eventSink(RoomListEvent.DeclineInvite(menu.roomSummary, false))
@@ -74,7 +84,8 @@ private fun RoomListDeclineInviteMenuContent(
Column(
modifier = Modifier
.fillMaxWidth()
- .padding(all = 16.dp),
+ .padding(all = 16.dp)
+ .verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
@@ -112,16 +123,15 @@ private fun RoomListDeclineInviteMenuContent(
}
}
-// TODO This component should be seen in [RoomListView] @Preview but it doesn't show up.
-// see: https://issuetracker.google.com/issues/283843380
-// Remove this preview when the issue is fixed.
@PreviewsDayNight
@Composable
-internal fun RoomListDeclineInviteMenuContentPreview() = ElementPreview {
- RoomListDeclineInviteMenuContent(
- roomName = "Room name",
- onCancelClick = {},
- onDeclineClick = {},
+internal fun RoomListDeclineInviteMenuPreview(
+ @PreviewParameter(RoomListStateDeclineInviteMenuShownProvider::class) menu: RoomListState.DeclineInviteMenu.Shown,
+) = ElementPreview {
+ RoomListDeclineInviteMenu(
+ menu = menu,
+ canReportRoom = false,
onDeclineAndBlockClick = {},
+ eventSink = {},
)
}
diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateDeclineInviteMenuShownProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateDeclineInviteMenuShownProvider.kt
new file mode 100644
index 0000000000..73d4785e96
--- /dev/null
+++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateDeclineInviteMenuShownProvider.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.features.home.impl.roomlist
+
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
+import io.element.android.features.home.impl.model.RoomListRoomSummary
+import io.element.android.features.home.impl.model.aRoomListRoomSummary
+
+open class RoomListStateDeclineInviteMenuShownProvider : PreviewParameterProvider {
+ override val values: Sequence
+ get() = sequenceOf(
+ aDeclineInviteMenuShown(),
+ aDeclineInviteMenuShown(
+ aRoomListRoomSummary(
+ name = LoremIpsum(500).values.first(),
+ )
+ ),
+ aDeclineInviteMenuShown(
+ aRoomListRoomSummary(
+ name = null,
+ )
+ ),
+ )
+}
+
+internal fun aDeclineInviteMenuShown(
+ roomSummary: RoomListRoomSummary = aRoomListRoomSummary(),
+) = RoomListState.DeclineInviteMenu.Shown(
+ roomSummary = roomSummary,
+)
diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersView.kt
index fb77c74203..1f0d6ff7d9 100644
--- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersView.kt
+++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersView.kt
@@ -81,7 +81,8 @@ fun SpaceFiltersView(
if (state is SpaceFiltersState.Selecting) {
state.eventSink(SpaceFiltersEvent.Selecting.Cancel)
}
- }
+ },
+ scrollable = false,
) {
Box(
modifier = Modifier
diff --git a/features/home/impl/src/main/res/values-pt/translations.xml b/features/home/impl/src/main/res/values-pt/translations.xml
index 9870014904..6c6a72930e 100644
--- a/features/home/impl/src/main/res/values-pt/translations.xml
+++ b/features/home/impl/src/main/res/values-pt/translations.xml
@@ -6,7 +6,7 @@
"O toque de notificação foi atualizado — mais claro, mais rápido e menos perturbador."
"Atualizámos os seus sons"
"Recupera a tua identidade criptográfica e o histórico de mensagens com uma chave de recuperação se tiveres perdido todos os teus dispositivos existentes."
- "Configurar recuperação"
+ "Chave de recuperação"
"Configurar a recuperação"
"Confirma a tua chave de recuperação para manteres o acesso ao teu armazenamento de chaves e ao histórico de mensagens."
"Introduz a tua chave de recuperação"
diff --git a/features/home/impl/src/main/res/values-ro/translations.xml b/features/home/impl/src/main/res/values-ro/translations.xml
index e4a80b4fb7..be1933e160 100644
--- a/features/home/impl/src/main/res/values-ro/translations.xml
+++ b/features/home/impl/src/main/res/values-ro/translations.xml
@@ -5,9 +5,9 @@
"Nu primiți notificări?"
"Sunetul pentru notificări a fost actualizat — mai clar, mai rapid și mai puțin perturbatoar."
"Am reîmprospătat sunetele"
- "Recuperați-vă identitatea criptografică și mesajele anterioare cu o cheie de recuperare dacă ați pierdut toate dispozitivele existente."
- "Configurați recuperarea"
- "Configurați recuperarea pentru a vă proteja contul"
+ "Chaturile dumneavoastră sunt salvate automat cu criptare end-to-end. Pentru a restaura această copie de rezervă și a vă păstra identitatea digitală atunci când pierzdeți accesul la toate dispozitivele dumneavoastră, veți avea nevoie de cheia de recuperare."
+ "Obțineți cheia de recuperare"
+ "Faceți un backup al mesajelor"
"Backup-ul pentru chat nu este sincronizat. Trebuie să confirmați cheia de recuperare pentru a menține accesul la backup."
"Introduceți cheia de recuperare"
"Ați uitat cheia de recuperare?"
diff --git a/features/home/impl/src/main/res/values-zh/translations.xml b/features/home/impl/src/main/res/values-zh/translations.xml
index e0704c52a6..39900a6f0b 100644
--- a/features/home/impl/src/main/res/values-zh/translations.xml
+++ b/features/home/impl/src/main/res/values-zh/translations.xml
@@ -1,56 +1,56 @@
- "请关闭本应用的电池优化设置,确保不错过任何消息通知。"
+ "对此 app 禁用电池优化以确保不错过任何通知。"
"禁用优化"
"通知未送达?"
- "您的通知提示音已升级 - 更清晰、更快速、干扰更少。"
- "我们已更新您的声音"
- "生成新的恢复密钥,该密钥可用于在您无法访问设备时恢复加密的消息历史记录。"
+ "通知提示音已升级:更清晰、更快速、干扰更少。"
+ "我们已更新你的声音"
+ "你的聊天已被端到端加密自动备份。如果你无法访问所有设备,则需要使用恢复密钥并保留数字身份。"
"获取恢复密钥"
"备份聊天"
"确认恢复密钥,以保持对密钥存储和消息历史的访问。"
"输入恢复密钥"
"忘记了恢复密钥?"
"你的密钥存储已不同步"
- "为确保您不会错过重要来电,请更改设置以允许锁屏时的全屏通知。"
+ "为确保你不会错过重要来电,请更改设置以允许锁屏时的全屏通知。"
"提升通话体验"
- "全部聊天"
+ "聊天"
"空间"
- "您确定要拒绝加入 %1$s 的邀请吗?"
+ "你确定要拒绝加入 %1$s 的邀请?"
"拒绝邀请"
- "您确定要拒绝与 %1$s 开始私聊吗?"
+ "你确定要拒绝与 %1$s 私聊?"
"拒绝聊天"
"没有邀请"
- "%1$s (%2$s)邀请了你"
- "这是一个一次性的过程,感谢您的等待。"
- "设置您的账户。"
- "创建新的对话或聊天室"
+ "%1$s(%2$s)邀请了你"
+ "此为一次性流程,感谢等待。"
+ "设置账户。"
+ "创建新的对话或房间"
"清除筛选条件"
"通过向某人发送消息来开始。"
- "还没有聊天。"
- "收藏夹"
- "可以在聊天设置里将聊天添加到收藏夹中。
-现在,可以取消选择过滤器以查看其他对话。"
- "您未收藏任何聊天"
+ "暂无聊天。"
+ "收藏"
+ "可以在聊天设置里将聊天添加到收藏夹。
+现在可以取消选择筛选器以查看其它对话。"
+ "你尚未收藏任何聊天"
"邀请"
"没有待处理的邀请。"
"低优先级"
- "您还没有任何低优先级聊天"
- "您可以取消选择过滤器以查看其他对话"
- "您没有关于此选项的聊天"
- "用户"
- "目前您还没有私信"
- "聊天室"
- "您尚未进入任何聊天室"
+ "你暂无任何低优先级聊天"
+ "你可以取消选择筛选器以查看其它对话"
+ "你暂无适用于此选项的聊天"
+ "人员"
+ "你暂无任何私聊"
+ "房间"
+ "你尚未进入任何房间"
"未读"
"恭喜!
没有任何未读消息!"
- "加入请求已发送"
- "全部聊天"
- "标记为已读"
- "标记为未读"
+ "加入申请已发送"
+ "聊天"
+ "设为已读"
+ "设为未读"
"此房间已升级"
- "您的空间"
- "您似乎正在使用新设备。使用另一台设备进行验证以访问您的加密消息。"
+ "你的空间"
+ "你似乎正在使用新设备。使用另一台设备进行验证以访问加密消息。"
"验证是你本人"
diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt
index 4c361b47f3..de5760c1bd 100644
--- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt
+++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt
@@ -6,10 +6,13 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.home.impl.filters
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.home.impl.R
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
@@ -17,23 +20,20 @@ import io.element.android.libraries.testtags.TestTags
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.pressTag
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RoomListFiltersViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on filters generates expected Event`() {
+ fun `clicking on filters generates expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setContent {
+ setContent {
RoomListFiltersView(
state = aRoomListFiltersState(eventSink = eventsRecorder),
)
}
- rule.clickOn(R.string.screen_roomlist_filter_rooms)
+ clickOn(R.string.screen_roomlist_filter_rooms)
eventsRecorder.assertList(
listOf(
RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms),
@@ -42,9 +42,9 @@ class RoomListFiltersViewTest {
}
@Test
- fun `clicking on clear filters generates expected Event`() {
+ fun `clicking on clear filters generates expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setContent {
+ setContent {
RoomListFiltersView(
state = aRoomListFiltersState(
filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) },
@@ -52,7 +52,7 @@ class RoomListFiltersViewTest {
),
)
}
- rule.pressTag(TestTags.homeScreenClearFilters.value)
+ pressTag(TestTags.homeScreenClearFilters.value)
eventsRecorder.assertList(
listOf(
RoomListFiltersEvent.ClearSelectedFilters,
diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt
index 6be5fe4c16..5fa2adf9d6 100644
--- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt
+++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.home.impl.roomlist
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.home.impl.R
import io.element.android.libraries.matrix.api.core.RoomId
@@ -20,23 +23,20 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.setSafeContent
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RoomListContextMenuTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on Mark as read generates expected Events`() {
+ fun `clicking on Mark as read generates expected Events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val contextMenu = aContextMenuShown(hasNewContent = true)
- rule.setRoomListContextMenu(
+ setRoomListContextMenu(
contextMenu = contextMenu,
eventSink = eventsRecorder,
)
- rule.clickOn(R.string.screen_roomlist_mark_as_read)
+ clickOn(R.string.screen_roomlist_mark_as_read)
eventsRecorder.assertList(
listOf(
RoomListEvent.HideContextMenu,
@@ -46,14 +46,14 @@ class RoomListContextMenuTest {
}
@Test
- fun `clicking on Mark as unread generates expected Events`() {
+ fun `clicking on Mark as unread generates expected Events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val contextMenu = aContextMenuShown(hasNewContent = false)
- rule.setRoomListContextMenu(
+ setRoomListContextMenu(
contextMenu = contextMenu,
eventSink = eventsRecorder,
)
- rule.clickOn(R.string.screen_roomlist_mark_as_unread)
+ clickOn(R.string.screen_roomlist_mark_as_unread)
eventsRecorder.assertList(
listOf(
RoomListEvent.HideContextMenu,
@@ -63,14 +63,14 @@ class RoomListContextMenuTest {
}
@Test
- fun `clicking on Leave room generates expected Events`() {
+ fun `clicking on Leave room generates expected Events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val contextMenu = aContextMenuShown(isDm = false)
- rule.setRoomListContextMenu(
+ setRoomListContextMenu(
contextMenu = contextMenu,
eventSink = eventsRecorder,
)
- rule.clickOn(CommonStrings.action_leave_room)
+ clickOn(CommonStrings.action_leave_room)
eventsRecorder.assertList(
listOf(
RoomListEvent.HideContextMenu,
@@ -80,48 +80,48 @@ class RoomListContextMenuTest {
}
@Test
- fun `clicking on Report room invokes the expected callback and generates expected Event`() {
+ fun `clicking on Report room invokes the expected callback and generates expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val contextMenu = aContextMenuShown()
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
- rule.setRoomListContextMenu(
+ setRoomListContextMenu(
contextMenu = contextMenu,
canReportRoom = true,
eventSink = eventsRecorder,
onRoomSettingsClick = EnsureNeverCalledWithParam(),
onReportRoomClick = callback,
)
- rule.clickOn(CommonStrings.action_report_room)
+ clickOn(CommonStrings.action_report_room)
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
callback.assertSuccess()
}
@Test
- fun `clicking on Settings invokes the expected callback and generates expected Event`() {
+ fun `clicking on Settings invokes the expected callback and generates expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val contextMenu = aContextMenuShown()
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
- rule.setRoomListContextMenu(
+ setRoomListContextMenu(
contextMenu = contextMenu,
eventSink = eventsRecorder,
onRoomSettingsClick = callback,
)
- rule.clickOn(CommonStrings.common_settings)
+ clickOn(CommonStrings.common_settings)
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
callback.assertSuccess()
}
@Test
- fun `clicking on Favourites generates expected Event`() {
+ fun `clicking on Favourites generates expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val contextMenu = aContextMenuShown(isDm = false, isFavorite = false)
val callback = EnsureNeverCalledWithParam()
- rule.setRoomListContextMenu(
+ setRoomListContextMenu(
contextMenu = contextMenu,
eventSink = eventsRecorder,
onRoomSettingsClick = callback,
)
- rule.clickOn(CommonStrings.common_favourite)
+ clickOn(CommonStrings.common_favourite)
eventsRecorder.assertList(
listOf(
RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, true),
@@ -129,7 +129,7 @@ class RoomListContextMenuTest {
)
}
- private fun AndroidComposeTestRule<*, *>.setRoomListContextMenu(
+ private fun AndroidComposeUiTest.setRoomListContextMenu(
contextMenu: RoomListState.ContextMenu.Shown,
canReportRoom: Boolean = false,
eventSink: (RoomListEvent) -> Unit,
diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt
index d7f509fda4..c8bba05e52 100644
--- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt
+++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt
@@ -6,10 +6,12 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.home.impl.roomlist
-import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.home.impl.model.aRoomListRoomSummary
import io.element.android.libraries.ui.strings.CommonStrings
@@ -18,19 +20,16 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.setSafeContent
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RoomListDeclineInviteMenuTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on decline emits the expected Events`() {
+ fun `clicking on decline emits the expected Events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
- rule.setSafeContent {
+ setSafeContent {
RoomListDeclineInviteMenu(
menu = menu,
canReportRoom = false,
@@ -38,7 +37,7 @@ class RoomListDeclineInviteMenuTest {
eventSink = eventsRecorder,
)
}
- rule.clickOn(CommonStrings.action_decline)
+ clickOn(CommonStrings.action_decline)
eventsRecorder.assertList(
listOf(
RoomListEvent.HideDeclineInviteMenu,
@@ -48,10 +47,10 @@ class RoomListDeclineInviteMenuTest {
}
@Test
- fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() {
+ fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
- rule.setSafeContent {
+ setSafeContent {
RoomListDeclineInviteMenu(
menu = menu,
canReportRoom = true,
@@ -59,16 +58,16 @@ class RoomListDeclineInviteMenuTest {
eventSink = eventsRecorder,
)
}
- rule.clickOn(CommonStrings.action_decline_and_block)
+ clickOn(CommonStrings.action_decline_and_block)
val expectedEvents = listOf(RoomListEvent.HideDeclineInviteMenu)
eventsRecorder.assertList(expectedEvents)
}
@Test
- fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() {
+ fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
- rule.setSafeContent {
+ setSafeContent {
RoomListDeclineInviteMenu(
menu = menu,
canReportRoom = false,
@@ -76,7 +75,7 @@ class RoomListDeclineInviteMenuTest {
eventSink = eventsRecorder,
)
}
- rule.clickOn(CommonStrings.action_decline_and_block)
+ clickOn(CommonStrings.action_decline_and_block)
val expectedEvents = listOf(
RoomListEvent.HideDeclineInviteMenu,
RoomListEvent.DeclineInvite(menu.roomSummary, blockUser = true),
@@ -85,10 +84,10 @@ class RoomListDeclineInviteMenuTest {
}
@Test
- fun `clicking on cancel emits the expected Event`() {
+ fun `clicking on cancel emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
- rule.setSafeContent {
+ setSafeContent {
RoomListDeclineInviteMenu(
menu = menu,
canReportRoom = false,
@@ -96,7 +95,7 @@ class RoomListDeclineInviteMenuTest {
eventSink = eventsRecorder,
)
}
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertList(listOf(RoomListEvent.HideDeclineInviteMenu))
}
}
diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt
index 8402a921ca..b8d61994fa 100644
--- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt
+++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt
@@ -6,16 +6,19 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.home.impl.roomlist
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.home.impl.HomeView
import io.element.android.features.home.impl.R
@@ -32,22 +35,17 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.setSafeContent
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class RoomListViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Config(qualifiers = "h1024dp")
@Test
- fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() {
+ fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setRoomListView(
+ setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
eventSink = eventsRecorder,
@@ -62,9 +60,9 @@ class RoomListViewTest {
}
@Test
- fun `clicking on close recovery key banner emits the expected Event`() {
+ fun `clicking on close recovery key banner emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setRoomListView(
+ setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
eventSink = eventsRecorder,
@@ -74,15 +72,15 @@ class RoomListViewTest {
// Remove automatic initial events
eventsRecorder.clear()
- val close = rule.activity.getString(CommonStrings.action_close)
- rule.onNodeWithContentDescription(close).performClick()
+ val close = activity!!.getString(CommonStrings.action_close)
+ onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
}
@Test
- fun `clicking on close setup key banner emits the expected Event`() {
+ fun `clicking on close setup key banner emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setRoomListView(
+ setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
eventSink = eventsRecorder,
@@ -92,16 +90,16 @@ class RoomListViewTest {
// Remove automatic initial events
eventsRecorder.clear()
- val close = rule.activity.getString(CommonStrings.action_close)
- rule.onNodeWithContentDescription(close).performClick()
+ val close = activity!!.getString(CommonStrings.action_close)
+ onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
}
@Test
- fun `clicking on continue recovery key banner invokes the expected callback`() {
+ fun `clicking on continue recovery key banner invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
ensureCalledOnce { callback ->
- rule.setRoomListView(
+ setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
eventSink = eventsRecorder,
@@ -112,17 +110,17 @@ class RoomListViewTest {
// Remove automatic initial events
eventsRecorder.clear()
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertEmpty()
}
}
@Test
- fun `clicking on continue setup key banner invokes the expected callback`() {
+ fun `clicking on continue setup key banner invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
ensureCalledOnce { callback ->
- rule.setRoomListView(
+ setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
eventSink = eventsRecorder,
@@ -131,28 +129,28 @@ class RoomListViewTest {
)
// Remove automatic initial events
eventsRecorder.clear()
- rule.clickOn(R.string.banner_set_up_recovery_submit)
+ clickOn(R.string.banner_set_up_recovery_submit)
eventsRecorder.assertEmpty()
}
}
@Test
- fun `clicking on start chat when the session has no room invokes the expected callback`() {
+ fun `clicking on start chat when the session has no room invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setRoomListView(
+ setRoomListView(
state = aRoomListState(
eventSink = eventsRecorder,
contentState = anEmptyContentState(),
),
onCreateRoomClick = callback,
)
- rule.clickOn(CommonStrings.action_start_chat)
+ clickOn(CommonStrings.action_start_chat)
}
}
@Test
- fun `clicking on a room invokes the expected callback`() {
+ fun `clicking on a room invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aRoomListState(
eventSink = eventsRecorder,
@@ -161,7 +159,7 @@ class RoomListViewTest {
it.displayType == RoomSummaryDisplayType.ROOM
}
ensureCalledOnceWithParam(room0.roomId) { callback ->
- rule.setRoomListView(
+ setRoomListView(
state = state,
onRoomClick = callback,
)
@@ -169,14 +167,14 @@ class RoomListViewTest {
// Remove automatic initial events
eventsRecorder.clear()
- rule.onNodeWithText(room0.latestEvent.content().toString()).performClick()
+ onNodeWithText(room0.latestEvent.content().toString()).performClick()
}
eventsRecorder.assertEmpty()
}
@Test
- fun `clicking on a room twice invokes the expected callback only once`() {
+ fun `clicking on a room twice invokes the expected callback only once`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aRoomListState(
eventSink = eventsRecorder,
@@ -185,13 +183,13 @@ class RoomListViewTest {
it.displayType == RoomSummaryDisplayType.ROOM
}
ensureCalledOnceWithParam(room0.roomId) { callback ->
- rule.setRoomListView(
+ setRoomListView(
state = state,
onRoomClick = callback,
)
// Remove automatic initial events
eventsRecorder.clear()
- rule.onNodeWithText(room0.latestEvent.content().toString())
+ onNodeWithText(room0.latestEvent.content().toString())
.performClick()
.performClick()
}
@@ -199,7 +197,7 @@ class RoomListViewTest {
}
@Test
- fun `long clicking on a room emits the expected Event`() {
+ fun `long clicking on a room emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aRoomListState(
eventSink = eventsRecorder,
@@ -207,18 +205,18 @@ class RoomListViewTest {
val room0 = state.contentAsRooms().summaries.first {
it.displayType == RoomSummaryDisplayType.ROOM
}
- rule.setRoomListView(
+ setRoomListView(
state = state,
)
// Remove automatic initial events
eventsRecorder.clear()
- rule.onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() }
+ onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() }
eventsRecorder.assertSingle(RoomListEvent.ShowContextMenu(room0))
}
@Test
- fun `clicking on a room setting invokes the expected callback and emits expected Event`() {
+ fun `clicking on a room setting invokes the expected callback and emits expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aRoomListState(
contextMenu = aContextMenuShown(),
@@ -226,7 +224,7 @@ class RoomListViewTest {
)
val room0 = (state.contextMenu as RoomListState.ContextMenu.Shown).roomId
ensureCalledOnceWithParam(room0) { callback ->
- rule.setRoomListView(
+ setRoomListView(
state = state,
onRoomSettingsClick = callback,
)
@@ -234,14 +232,14 @@ class RoomListViewTest {
// Remove automatic initial events
eventsRecorder.clear()
- rule.clickOn(CommonStrings.common_settings)
+ clickOn(CommonStrings.common_settings)
}
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
}
@Test
- fun `clicking on accept and decline invite emits the expected Events`() {
+ fun `clicking on accept and decline invite emits the expected Events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aRoomListState(
eventSink = eventsRecorder,
@@ -249,13 +247,13 @@ class RoomListViewTest {
val invitedRoom = state.contentAsRooms().summaries.first {
it.displayType == RoomSummaryDisplayType.INVITE
}
- rule.setRoomListView(state = state)
+ setRoomListView(state = state)
// Remove automatic initial events
eventsRecorder.clear()
- rule.clickOn(CommonStrings.action_accept)
- rule.clickOn(CommonStrings.action_decline)
+ clickOn(CommonStrings.action_accept)
+ clickOn(CommonStrings.action_decline)
eventsRecorder.assertList(
listOf(
RoomListEvent.AcceptInvite(invitedRoom),
@@ -265,7 +263,7 @@ class RoomListViewTest {
}
}
-private fun AndroidComposeTestRule.setRoomListView(
+private fun AndroidComposeUiTest.setRoomListView(
state: RoomListState,
onRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
onSettingsClick: () -> Unit = EnsureNeverCalled(),
diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersViewTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersViewTest.kt
index 5c1325b107..d612d765b6 100644
--- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersViewTest.kt
+++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersViewTest.kt
@@ -5,34 +5,32 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.home.impl.spacefilters
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
import io.element.android.tests.testutils.EventsRecorder
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SpaceFiltersViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on a filter with alias shows display name and alias`() {
+ fun `clicking on a filter with alias shows display name and alias`() = runAndroidComposeUiTest {
val filter = aSpaceServiceFilter(
displayName = "Test Space",
canonicalAlias = A_ROOM_ALIAS,
)
val eventsRecorder = EventsRecorder()
- rule.setSpaceFiltersView(
+ setSpaceFiltersView(
state = aSelectingSpaceFiltersState(
availableFilters = listOf(filter),
eventSink = eventsRecorder,
@@ -40,20 +38,20 @@ class SpaceFiltersViewTest {
)
// Both display name and alias should be visible
- rule.onNodeWithText(filter.spaceRoom.displayName).assertExists()
- rule.onNodeWithText(A_ROOM_ALIAS.value).assertExists()
+ onNodeWithText(filter.spaceRoom.displayName).assertExists()
+ onNodeWithText(A_ROOM_ALIAS.value).assertExists()
- rule.onNodeWithText(filter.spaceRoom.displayName).performClick()
+ onNodeWithText(filter.spaceRoom.displayName).performClick()
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter))
}
@Test
- fun `multiple filters are displayed and clickable`() {
+ fun `multiple filters are displayed and clickable`() = runAndroidComposeUiTest {
val filter1 = aSpaceServiceFilter(displayName = "Space One")
val filter2 = aSpaceServiceFilter(displayName = "Space Two")
val eventsRecorder = EventsRecorder()
- rule.setSpaceFiltersView(
+ setSpaceFiltersView(
state = aSelectingSpaceFiltersState(
availableFilters = listOf(filter1, filter2),
eventSink = eventsRecorder,
@@ -61,17 +59,17 @@ class SpaceFiltersViewTest {
)
// Both filters should be visible
- rule.onNodeWithText(filter1.spaceRoom.displayName).assertExists()
- rule.onNodeWithText(filter2.spaceRoom.displayName).assertExists()
+ onNodeWithText(filter1.spaceRoom.displayName).assertExists()
+ onNodeWithText(filter2.spaceRoom.displayName).assertExists()
// Click on second filter
- rule.onNodeWithText(filter2.spaceRoom.displayName).performClick()
+ onNodeWithText(filter2.spaceRoom.displayName).performClick()
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter2))
}
}
-private fun AndroidComposeTestRule.setSpaceFiltersView(
+private fun AndroidComposeUiTest.setSpaceFiltersView(
state: SpaceFiltersState,
) {
setContent {
diff --git a/features/invite/impl/build.gradle.kts b/features/invite/impl/build.gradle.kts
index 80b98464f7..e033f2740c 100644
--- a/features/invite/impl/build.gradle.kts
+++ b/features/invite/impl/build.gradle.kts
@@ -33,6 +33,7 @@ dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui)
+ implementation(projects.libraries.sessionStorage.api)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
implementation(projects.services.analytics.api)
diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteStateProvider.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteStateProvider.kt
index 3f8bf93afa..db2e76c1c3 100644
--- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteStateProvider.kt
+++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteStateProvider.kt
@@ -15,6 +15,7 @@ import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInv
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
import io.element.android.features.invite.impl.AcceptInvite
import io.element.android.libraries.architecture.AsyncAction
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
import io.element.android.libraries.matrix.api.core.RoomId
open class AcceptDeclineInviteStateProvider : PreviewParameterProvider {
@@ -26,7 +27,7 @@ open class AcceptDeclineInviteStateProvider : PreviewParameterProvider
- "您将不会看到来自该用户的任何信息或房间邀请"
+ "你将不会看到来自该用户的任何消息或房间邀请"
"屏蔽用户"
- "向您的帐户提供商举报此房间。"
- "描述举报的原因…"
+ "向账户提供者举报此房间。"
+ "描述举报的理由…"
"拒绝并屏蔽"
- "您确定要拒绝加入 %1$s 的邀请吗?"
+ "你确定要拒绝加入 %1$s 的邀请?"
"拒绝邀请"
- "您确定要拒绝与 %1$s 开始私聊吗?"
+ "你确定要拒绝与 %1$s 私聊?"
"拒绝聊天"
"没有邀请"
- "%1$s (%2$s)邀请了你"
- "是的,拒绝并屏蔽"
- "您确定要拒绝加入此房间的邀请吗?这也将阻止 %1$s 与您联系或邀请您加入房间。"
+ "%1$s(%2$s)邀请了你"
+ "是,拒绝并屏蔽"
+ "你确定要拒绝此房间的加入邀请?这也将阻止 %1$s 与你联系或邀请你加入房间。"
"拒绝邀请并屏蔽"
"拒绝并屏蔽"
diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt
index 299fec8565..e915696de4 100644
--- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt
+++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt
@@ -6,13 +6,16 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.invite.impl.declineandblock
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.invite.impl.R
import io.element.android.libraries.ui.strings.CommonStrings
@@ -21,98 +24,94 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DeclineAndBlockViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back invoke the expected callback`() {
+ fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setDeclineAndBlockView(
+ setDeclineAndBlockView(
aDeclineAndBlockState(
eventSink = eventsRecorder,
),
onBackClick = it
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `clicking on decline when enabled emits the expected event`() {
+ fun `clicking on decline when enabled emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDeclineAndBlockView(
+ setDeclineAndBlockView(
aDeclineAndBlockState(
blockUser = true,
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_decline)
+ clickOn(CommonStrings.action_decline)
eventsRecorder.assertSingle(DeclineAndBlockEvents.Decline)
}
@Test
- fun `clicking on decline when disabled does not emit event`() {
+ fun `clicking on decline when disabled does not emit event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
- rule.setDeclineAndBlockView(
+ setDeclineAndBlockView(
aDeclineAndBlockState(
blockUser = false,
reportRoom = false,
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_decline)
+ clickOn(CommonStrings.action_decline)
eventsRecorder.assertEmpty()
}
@Test
- fun `clicking on block option emits the expected event`() {
+ fun `clicking on block option emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDeclineAndBlockView(
+ setDeclineAndBlockView(
aDeclineAndBlockState(
blockUser = true,
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_decline_and_block_block_user_option_title)
+ clickOn(R.string.screen_decline_and_block_block_user_option_title)
eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleBlockUser)
}
@Test
- fun `clicking on report room option emits the expected event`() {
+ fun `clicking on report room option emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDeclineAndBlockView(
+ setDeclineAndBlockView(
aDeclineAndBlockState(
reportRoom = true,
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_report_room)
+ clickOn(CommonStrings.action_report_room)
eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleReportRoom)
}
@Test
- fun `typing text in the reason field emits the expected Event`() {
+ fun `typing text in the reason field emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDeclineAndBlockView(
+ setDeclineAndBlockView(
aDeclineAndBlockState(
reportRoom = true,
reportReason = "",
eventSink = eventsRecorder,
),
)
- rule.onNodeWithText("").performTextInput("Spam!")
+ onNodeWithText("").performTextInput("Spam!")
eventsRecorder.assertSingle(DeclineAndBlockEvents.UpdateReportReason("Spam!"))
}
}
-private fun AndroidComposeTestRule.setDeclineAndBlockView(
+private fun AndroidComposeUiTest.setDeclineAndBlockView(
state: DeclineAndBlockState,
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
diff --git a/features/invite/test/build.gradle.kts b/features/invite/test/build.gradle.kts
index 2df267f155..080ed765bb 100644
--- a/features/invite/test/build.gradle.kts
+++ b/features/invite/test/build.gradle.kts
@@ -16,6 +16,7 @@ android {
dependencies {
implementation(libs.coroutines.core)
+ implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrix.test)
implementation(projects.tests.testutils)
diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt
index 58b3fb67f6..b223d30617 100644
--- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt
+++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt
@@ -13,7 +13,6 @@ import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -37,8 +36,6 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope
-import io.element.android.libraries.featureflag.api.FeatureFlagService
-import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
@@ -74,7 +71,6 @@ class DefaultInvitePeoplePresenter(
private val coroutineDispatchers: CoroutineDispatchers,
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
private val appErrorStateService: AppErrorStateService,
- private val featureFlagService: FeatureFlagService,
private val matrixClient: MatrixClient,
) : InvitePeoplePresenter {
@AssistedFactory
@@ -93,8 +89,6 @@ class DefaultInvitePeoplePresenter(
val showSearchLoader = rememberSaveable { mutableStateOf(false) }
val sendInvitesAction = remember { mutableStateOf>(AsyncAction.Uninitialized) }
- val enableKeyShareOnInvite by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false)
-
val recentDirectRooms by produceState(emptyList(), roomMembers.value) {
if (roomMembers.value.isSuccess()) {
val activeMemberIds = roomMembers.value.dataOrNull().orEmpty()
@@ -137,12 +131,7 @@ class DefaultInvitePeoplePresenter(
val selectedUserIdentities = produceState(
emptyMap().toImmutableMap(),
selectedUsers.value,
- enableKeyShareOnInvite,
) {
- if (!enableKeyShareOnInvite) {
- return@produceState
- }
-
val selected = selectedUsers.value
val cached = value
@@ -213,7 +202,7 @@ class DefaultInvitePeoplePresenter(
}
}
is InvitePeopleEvents.SendInvites -> {
- if (enableKeyShareOnInvite && unknownUsers.isNotEmpty() && sendInvitesAction.value !is ConfirmingUnknownUserInvitation) {
+ if (unknownUsers.isNotEmpty() && sendInvitesAction.value !is ConfirmingUnknownUserInvitation) {
sendInvitesAction.value = ConfirmingUnknownUserInvitation(
unknownUsers
)
diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt
index c26b8de254..45b47fbc6e 100644
--- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt
+++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt
@@ -12,6 +12,11 @@ import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
+import io.element.android.libraries.designsystem.preview.USER_NAME_CAROL
+import io.element.android.libraries.designsystem.preview.USER_NAME_EVE
+import io.element.android.libraries.designsystem.preview.USER_NAME_JUSTIN
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUser
@@ -33,15 +38,15 @@ internal class DefaultInvitePeopleStateProvider : PreviewParameterProvider
"Již členem"
"Již pozván(a)"
+ "Momentálně s těmito kontakty nemáte žádné chaty. Před pokračováním potvrďte jejich pozvání do této místnosti."
+ "Momentálně s tímto kontaktem nemáte žádné chaty. Před pokračováním potvrďte pozvání do této místnosti."
+ "Pozvat nové kontakty do této místnosti?"
+ "Pozvat nový kontakt do této místnosti?"
diff --git a/features/invitepeople/impl/src/main/res/values-da/translations.xml b/features/invitepeople/impl/src/main/res/values-da/translations.xml
index fbb1814e9f..1754ef6e0d 100644
--- a/features/invitepeople/impl/src/main/res/values-da/translations.xml
+++ b/features/invitepeople/impl/src/main/res/values-da/translations.xml
@@ -2,4 +2,8 @@
"Allerede medlem"
"Allerede inviteret"
+ "Du har i øjeblikket ingen chats med disse kontakter. Bekræft deres invitation til dette rum, før du fortsætter."
+ "Du har i øjeblikket ingen chats med denne kontakt. Bekræft deres invitation til dette rum, før du fortsætter."
+ "Inviter nye kontakter til dette rum?"
+ "Inviter ny kontakt til dette rum?"
diff --git a/features/invitepeople/impl/src/main/res/values-fr/translations.xml b/features/invitepeople/impl/src/main/res/values-fr/translations.xml
index dcc16f58cf..b5df870002 100644
--- a/features/invitepeople/impl/src/main/res/values-fr/translations.xml
+++ b/features/invitepeople/impl/src/main/res/values-fr/translations.xml
@@ -2,4 +2,8 @@
"Déjà membre"
"Déjà invité(e)"
+ "Vous n’avez actuellement aucune conversation avec ces contacts. Veuillez confirmer leur invitation à rejoindre ce salon avant de continuer."
+ "Vous n’avez actuellement aucune conversation avec ce contact. Veuillez confirmer son invitation à rejoindre ce salon avant de continuer."
+ "Inviter de nouveaux contacts dans ce salon ?"
+ "Inviter un nouveau contact dans ce salon ?"
diff --git a/features/invitepeople/impl/src/main/res/values-hr/translations.xml b/features/invitepeople/impl/src/main/res/values-hr/translations.xml
index 66031c5fd7..471b79d709 100644
--- a/features/invitepeople/impl/src/main/res/values-hr/translations.xml
+++ b/features/invitepeople/impl/src/main/res/values-hr/translations.xml
@@ -2,4 +2,8 @@
"Već je član"
"Već je pozvan/a"
+ "Trenutno nemate razgovora s ovim kontaktima. Potvrdite da ih želite pozvati u ovu sobu prije nego što nastavite."
+ "Trenutno nemate razgovora s ovim kontaktom. Potvrdite da ste ga pozvali u ovu sobu prije nego što nastavite."
+ "Pozvati nove kontakte u ovu sobu?"
+ "Pozvati novi kontakt u ovu sobu?"
diff --git a/features/invitepeople/impl/src/main/res/values-hu/translations.xml b/features/invitepeople/impl/src/main/res/values-hu/translations.xml
index 16f35b018c..de2cab0d73 100644
--- a/features/invitepeople/impl/src/main/res/values-hu/translations.xml
+++ b/features/invitepeople/impl/src/main/res/values-hu/translations.xml
@@ -2,4 +2,8 @@
"Már tag"
"Már meghívták"
+ "Jelenleg nincsenek csevegései ezekkel a kapcsolatokkal. Mielőtt továbbmenne, erősítse meg, hogy meghívja őket ebbe a szobába."
+ "Jelenleg nincsenek beszélgetései ezzel a személlyel. A folytatás előtt erősítse meg, hogy meghívja ebbe a szobába."
+ "Új személyeket hív meg ebbe a szobába?"
+ "Új személyt hív meg ebbe a szobába?"
diff --git a/features/invitepeople/impl/src/main/res/values-ja/translations.xml b/features/invitepeople/impl/src/main/res/values-ja/translations.xml
index eefa446d79..db5b91ca2b 100644
--- a/features/invitepeople/impl/src/main/res/values-ja/translations.xml
+++ b/features/invitepeople/impl/src/main/res/values-ja/translations.xml
@@ -2,4 +2,8 @@
"既に参加しています"
"既に招待しています"
+ "これらの人物とのチャットがありません。はじめに、招待の状況を確認してください。"
+ "この連絡先とのチャットがありません。はじめに、招待の状況を確認してください。"
+ "このルームに新しい連絡先を招待しますか?"
+ "このルームに新しい連絡先を招待しますか?"
diff --git a/features/invitepeople/impl/src/main/res/values-zh/translations.xml b/features/invitepeople/impl/src/main/res/values-zh/translations.xml
index b1e0e953f8..7b1bb29288 100644
--- a/features/invitepeople/impl/src/main/res/values-zh/translations.xml
+++ b/features/invitepeople/impl/src/main/res/values-zh/translations.xml
@@ -2,4 +2,8 @@
"已经是成员"
"已邀请"
+ "你与这些联系人暂无任何聊天。请确认对方被邀请到此房间后再继续。"
+ "你与此人暂无任何聊天。请确认对方被邀请到此房间后再继续。"
+ "邀请新联系人到此房间?"
+ "邀请新联系人到此房间?"
diff --git a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt
index a1d72010f6..4ecd74a42f 100644
--- a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt
+++ b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt
@@ -15,9 +15,6 @@ import io.element.android.features.invitepeople.api.InvitePeopleEvents
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
-import io.element.android.libraries.featureflag.api.FeatureFlagService
-import io.element.android.libraries.featureflag.api.FeatureFlags
-import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@@ -323,7 +320,7 @@ internal class DefaultInvitePeoplePresenterTest {
val initialState = awaitItemAsDefault()
skipItems(1)
- val selectedUser = aMatrixUser()
+ val selectedUser = aMatrixUser(displayName = "John Doe")
initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(selectedUser))
@@ -361,7 +358,7 @@ internal class DefaultInvitePeoplePresenterTest {
val initialState = awaitItemAsDefault()
skipItems(1)
- val selectedUser = aMatrixUser()
+ val selectedUser = aMatrixUser(displayName = "John Doe")
// Given a query is made
initialState.searchQuery.setTextAndPlaceCursorAtEnd("some query")
@@ -405,10 +402,14 @@ internal class DefaultInvitePeoplePresenterTest {
val inviteUserResult = lambdaRecorder> { userId: UserId ->
Result.success(Unit)
}
+ val encryptionService = FakeEncryptionService(
+ getUserIdentityResult = { _ -> Result.success(null) },
+ )
val presenter = createDefaultInvitePeoplePresenter(
userRepository = repository,
inviteUserResult = inviteUserResult,
- coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
+ coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
+ matrixClient = FakeMatrixClient(encryptionService = encryptionService),
)
presenter.test {
val initialState = awaitItem()
@@ -451,13 +452,18 @@ internal class DefaultInvitePeoplePresenterTest {
Result.failure(AN_EXCEPTION)
}
val showErrorResResult = lambdaRecorder { _, _ -> }
+
+ val encryptionService = FakeEncryptionService(
+ getUserIdentityResult = { _ -> Result.success(null) },
+ )
val presenter = createDefaultInvitePeoplePresenter(
userRepository = repository,
inviteUserResult = inviteUserResult,
coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
appErrorStateService = FakeAppErrorStateService(
showErrorResResult = showErrorResResult,
- )
+ ),
+ matrixClient = FakeMatrixClient(encryptionService = encryptionService),
)
presenter.test {
val initialState = awaitItem()
@@ -632,15 +638,11 @@ internal class DefaultInvitePeoplePresenterTest {
val encryptionService = FakeEncryptionService(
getUserIdentityResult = getUserIdentityResult
)
- val featureFlagService = FakeFeatureFlagService().apply {
- setFeatureEnabled(FeatureFlags.EnableKeyShareOnInvite, true)
- }
val presenter = createDefaultInvitePeoplePresenter(
coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
inviteUserResult = inviteUserResult,
matrixClient = FakeMatrixClient(encryptionService = encryptionService),
- featureFlagService = featureFlagService
)
presenter.test {
val initialState = awaitItem()
@@ -703,15 +705,11 @@ internal class DefaultInvitePeoplePresenterTest {
val encryptionService = FakeEncryptionService(
getUserIdentityResult = getUserIdentityResult
)
- val featureFlagService = FakeFeatureFlagService().apply {
- setFeatureEnabled(FeatureFlags.EnableKeyShareOnInvite, true)
- }
val presenter = createDefaultInvitePeoplePresenter(
userRepository = repository,
coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
matrixClient = FakeMatrixClient(encryptionService = encryptionService),
- featureFlagService = featureFlagService
)
presenter.test {
val initialState = awaitItemAsDefault()
@@ -790,14 +788,10 @@ internal class DefaultInvitePeoplePresenterTest {
val encryptionService = FakeEncryptionService(
getUserIdentityResult = getUserIdentityResult
)
- val featureFlagService = FakeFeatureFlagService().apply {
- setFeatureEnabled(FeatureFlags.EnableKeyShareOnInvite, true)
- }
val presenter = createDefaultInvitePeoplePresenter(
coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
matrixClient = FakeMatrixClient(encryptionService = encryptionService),
- featureFlagService = featureFlagService
)
presenter.test {
val initialState = awaitItem()
@@ -878,7 +872,6 @@ fun TestScope.createDefaultInvitePeoplePresenter(
userRepository: UserRepository = FakeUserRepository(),
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
appErrorStateService: AppErrorStateService = FakeAppErrorStateService(),
- featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
matrixClient: MatrixClient = FakeMatrixClient(),
): DefaultInvitePeoplePresenter {
return DefaultInvitePeoplePresenter(
@@ -888,7 +881,6 @@ fun TestScope.createDefaultInvitePeoplePresenter(
coroutineDispatchers = coroutineDispatchers,
sessionCoroutineScope = backgroundScope,
appErrorStateService = appErrorStateService,
- featureFlagService = featureFlagService,
matrixClient = matrixClient,
)
}
diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt
index 7e5142321a..4bfa741321 100644
--- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt
+++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt
@@ -15,6 +15,9 @@ import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInvit
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
@@ -134,8 +137,8 @@ open class JoinRoomStateProvider : PreviewParameterProvider {
joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(
banSender = InviteSender(
userId = UserId("@alice:domain"),
- displayName = "Alice",
- avatarData = AvatarData("alice", "Alice", size = AvatarSize.InviteSender),
+ displayName = USER_NAME_ALICE,
+ avatarData = AvatarData("alice", USER_NAME_ALICE, size = AvatarSize.InviteSender),
membershipChangeReason = "spamming"
),
reason = "spamming",
@@ -222,7 +225,7 @@ fun aJoinRoomState(
internal fun anInviteSender(
userId: UserId = UserId("@bob:domain"),
- displayName: String = "Bob",
+ displayName: String = USER_NAME_BOB,
avatarData: AvatarData = AvatarData(userId.value, displayName, size = AvatarSize.InviteSender),
membershipChangeReason: String? = null,
) = InviteSender(
@@ -234,7 +237,7 @@ internal fun anInviteSender(
internal fun anInviteData(
roomId: RoomId = A_ROOM_ID,
- roomName: String = "Room name",
+ roomName: String = ROOM_NAME,
isDm: Boolean = false,
) = InviteData(
roomId = roomId,
diff --git a/features/joinroom/impl/src/main/res/values-zh/translations.xml b/features/joinroom/impl/src/main/res/values-zh/translations.xml
index d38f9d3427..e4de8dfec4 100644
--- a/features/joinroom/impl/src/main/res/values-zh/translations.xml
+++ b/features/joinroom/impl/src/main/res/values-zh/translations.xml
@@ -1,34 +1,34 @@
- "您已被 %1$s 封禁。"
+ "你已被 %1$s 封禁。"
"你已被此房间封禁"
"理由:%1$s。"
- "取消请求"
- "是的,取消"
- "您确定要取消加入此房间的请求吗?"
+ "取消申请"
+ "是,取消"
+ "你确定要取消加入此房间的申请?"
"取消加入申请"
- "是的,拒绝并屏蔽"
- "您确定要拒绝加入此房间的邀请吗?这也将阻止 %1$s 与您联系或邀请您加入房间。"
+ "是,拒绝并屏蔽"
+ "你确定要拒绝此房间的加入邀请?这也将阻止 %1$s 与你联系或邀请你加入房间。"
"拒绝邀请并屏蔽"
"拒绝并屏蔽"
"加入失败"
- "您需要被邀请加入,否则可能会受到访问限制。"
+ "你需要被邀请才能加入,否则可能会遭遇访问限制。"
"忘记"
- "您需要邀请才能加入"
+ "你需要被邀请才能加入"
"受邀于"
"加入"
- "您可能需要受到邀请或成为某个空间的成员才能加入。"
- "加入聊天室"
- "允许的字符数量 %2$d中的%1$d"
+ "你可能需要被邀请或成为某个空间的成员才能加入。"
+ "加入房间"
+ "允许的字符数量共 %2$d 个,当前为 %1$d 个"
"消息(可选)"
- "如果您的请求被接受,您将收到加入房间的邀请。"
- "加入请求已发送"
+ "如果你的申请被批准,你将收到加入房间的邀请。"
+ "加入申请已发送"
"无法显示房间预览。这可能是由于网络或服务器问题造成的。"
"无法显示此房间预览"
- "%1$s 尚不支持空间。您可以通过 Web 端访问空间"
- "空间尚不支持"
- "点击下面的按钮,系统将通知聊天室管理员。获得批准后将能够加入对话。"
- "只有聊天室成员才能查看消息历史记录。"
- "想加入此聊天室吗?"
+ "%1$s 暂不支持空间。你可以通过 Web 客户端访问空间。"
+ "空间尚未受到支持"
+ "点击以下按钮以通知房间管理员。获得批准后你将能加入对话。"
+ "只有房间成员才能查看消息历史。"
+ "想加入此房间吗?"
"预览不可用"
diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt
index 0a3b1ca3c6..e60d7da691 100644
--- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt
+++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.joinroom.impl
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.invite.api.InviteData
import io.element.android.features.invite.test.anInviteData
@@ -26,116 +29,112 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBack
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class JoinRoomViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back invoke the expected callback`() {
+ fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
eventSink = eventsRecorder,
),
onBackClick = it
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `clicking on Join room on CanJoin room emits the expected Event`() {
+ fun `clicking on Join room on CanJoin room emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_join_room_join_action)
+ clickOn(R.string.screen_join_room_join_action)
eventsRecorder.assertSingle(JoinRoomEvents.JoinRoom)
}
@Test
- fun `clicking on Knock room on CanKnock room emits the expected Event`() {
+ fun `clicking on Knock room on CanKnock room emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
knockMessage = "Knock knock",
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_join_room_knock_action)
+ clickOn(R.string.screen_join_room_knock_action)
eventsRecorder.assertSingle(JoinRoomEvents.KnockRoom)
}
@Test
- fun `clicking on closing Knock error emits the expected Event`() {
+ fun `clicking on closing Knock error emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
knockAction = AsyncAction.Failure(Exception("Error")),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_ok)
+ clickOn(CommonStrings.action_ok)
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
}
@Test
- fun `clicking on cancel knock request emit the expected Event`() {
+ fun `clicking on cancel knock request emit the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked),
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_join_room_cancel_knock_action)
+ clickOn(R.string.screen_join_room_cancel_knock_action)
eventsRecorder.assertSingle(JoinRoomEvents.CancelKnock(true))
}
@Test
- fun `clicking on closing Cancel Knock error emits the expected Event`() {
+ fun `clicking on closing Cancel Knock error emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked),
cancelKnockAction = AsyncAction.Failure(Exception("Error")),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_ok)
+ clickOn(CommonStrings.action_ok)
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
}
@Test
- fun `clicking on closing Join error emits the expected Event`() {
+ fun `clicking on closing Join error emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
joinAction = AsyncAction.Failure(Exception("Error")),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_ok)
+ clickOn(CommonStrings.action_ok)
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
}
@Test
- fun `when joining room is successful, the expected callback is invoked`() {
+ fun `when joining room is successful, the expected callback is invoked`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
joinAction = AsyncAction.Success(Unit),
eventSink = eventsRecorder,
@@ -146,53 +145,55 @@ class JoinRoomViewTest {
}
@Test
- fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
+ fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val inviteData = anInviteData()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, null)),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_accept)
+ clickOn(CommonStrings.action_accept)
eventsRecorder.assertSingle(JoinRoomEvents.AcceptInvite(inviteData))
}
@Test
- fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
+ fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val inviteData = anInviteData()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, null)),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_decline)
+ clickOn(CommonStrings.action_decline)
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, false))
}
@Test
fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and can report room, the expected callback is invoked`() {
- val eventsRecorder = EventsRecorder(expectEvents = false)
- val inviteData = anInviteData()
- val joinRoomState = aJoinRoomState(
- contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, aRoomMember().toInviteSender())),
- canReportRoom = true,
- eventSink = eventsRecorder,
- )
- ensureCalledOnceWithParam(inviteData) {
- rule.setJoinRoomView(
- state = joinRoomState,
- onDeclineInviteAndBlockUser = it,
+ runAndroidComposeUiTest {
+ val eventsRecorder = EventsRecorder(expectEvents = false)
+ val inviteData = anInviteData()
+ val joinRoomState = aJoinRoomState(
+ contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, aRoomMember().toInviteSender())),
+ canReportRoom = true,
+ eventSink = eventsRecorder,
)
- rule.clickOn(R.string.screen_join_room_decline_and_block_button_title)
+ ensureCalledOnceWithParam(inviteData) {
+ setJoinRoomView(
+ state = joinRoomState,
+ onDeclineInviteAndBlockUser = it,
+ )
+ clickOn(R.string.screen_join_room_decline_and_block_button_title)
+ }
}
}
@Test
- fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and cant report room, emits the expected Event`() {
+ fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and cant report room, emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val inviteData = anInviteData()
val joinRoomState = aJoinRoomState(
@@ -200,29 +201,29 @@ class JoinRoomViewTest {
canReportRoom = false,
eventSink = eventsRecorder,
)
- rule.setJoinRoomView(state = joinRoomState)
- rule.clickOn(R.string.screen_join_room_decline_and_block_button_title)
+ setJoinRoomView(state = joinRoomState)
+ clickOn(R.string.screen_join_room_decline_and_block_button_title)
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, true))
}
@Test
- fun `clicking on Retry when an error occurs emits the expected Event`() {
+ fun `clicking on Retry when an error occurs emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aFailureContentState(),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_retry)
+ clickOn(CommonStrings.action_retry)
eventsRecorder.assertSingle(JoinRoomEvents.RetryFetchingContent)
}
@Test
- fun `clicking on ok when user is unauthorized the expected callback`() {
+ fun `clicking on ok when user is unauthorized the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(),
joinAction = AsyncAction.Failure(JoinRoom.Failures.UnauthorizedJoin),
@@ -230,25 +231,25 @@ class JoinRoomViewTest {
),
onBackClick = it
)
- rule.clickOn(CommonStrings.action_ok)
+ clickOn(CommonStrings.action_ok)
}
}
@Test
- fun `clicking on forget when user is banned invokes the expected callback`() {
+ fun `clicking on forget when user is banned invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setJoinRoomView(
+ setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(null, null)),
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_join_room_forget_action)
+ clickOn(R.string.screen_join_room_forget_action)
eventsRecorder.assertSingle(JoinRoomEvents.ForgetRoom)
}
}
-private fun AndroidComposeTestRule.setJoinRoomView(
+private fun AndroidComposeUiTest.setJoinRoomView(
state: JoinRoomState,
onBackClick: () -> Unit = EnsureNeverCalled(),
onJoinSuccess: () -> Unit = EnsureNeverCalled(),
diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt
index 67f1aaae8f..7210e783fe 100644
--- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt
+++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt
@@ -11,6 +11,9 @@ package io.element.android.features.knockrequests.impl.banner
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
+import io.element.android.libraries.designsystem.preview.USER_NAME_CHARLIE
import kotlinx.collections.immutable.toImmutableList
class KnockRequestsBannerStateProvider : PreviewParameterProvider {
@@ -29,15 +32,15 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider
- "是的,全部接受"
- "您确定要接受所有加入请求吗?"
- "接受所有请求"
+ "是,全部接受"
+ "你确定要接受所有加入申请?"
+ "接受所有申请"
"全部接受"
- "我们无法接受所有请求。是否要再试一次?"
- "无法接受所有请求"
- "接受所有加入请求"
- "我们无法接受此请求。是否要再试一次?"
- "无法接受请求"
- "接受加入请求"
- "是的,拒绝并禁止"
- "您确定要拒绝并禁止吗%1$s?该用户将无法再次请求加入该房间。"
+ "我们无法接受所有申请。是否重试?"
+ "无法接受所有申请"
+ "接受所有加入申请"
+ "我们无法接受此申请。是否重试?"
+ "无法接受申请"
+ "接受加入申请"
+ "是,拒绝并禁止"
+ "你确定要拒绝并封禁 %1$s?该用户将无法再次申请加入该房间。"
"拒绝并禁止访问"
"拒绝并禁止访问"
- "是的,拒绝"
- "您确定要拒绝 %1$s 加入此房间的请求吗?"
+ "是,拒绝"
+ "你确定要拒绝 %1$s 加入此房间的申请?"
"拒绝访问"
"拒绝和禁止"
- "我们无法拒绝此请求。是否要再试一次?"
- "拒绝请求失败"
- "拒绝加入请求"
- "当有人请求加入房间时,您将能够在这里看到他们的请求。"
- "没有待处理的加入请求"
- "正在加载加入请求…"
+ "我们无法拒绝此申请。是否重试?"
+ "拒绝申请失败"
+ "拒绝加入申请"
+ "当有人申请加入房间时,你将能够在这里看到其申请。"
+ "暂无待处理的加入申请"
+ "正在加载加入申请…"
"申请加入"
- - "%1$s+ %2$d 其他人想加入这个房间"
+ - "%1$s、%2$d 及其他人想加入此房间"
"查看全部"
"接受"
diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt
index a9fea0905e..fc1600d8c8 100644
--- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt
+++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt
@@ -6,13 +6,16 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.knockrequests.impl.banner
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
@@ -21,35 +24,30 @@ import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class KnockRequestsBannerViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on view on single request invoke the expected callback`() {
+ fun `clicking on view on single request invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setKnockRequestsBannerView(
+ setKnockRequestsBannerView(
state = aKnockRequestsBannerState(
eventSink = eventsRecorder,
),
onViewRequestsClick = it
)
- rule.clickOn(R.string.screen_room_single_knock_request_view_button_title)
+ clickOn(R.string.screen_room_single_knock_request_view_button_title)
}
}
@Test
- fun `clicking on view all when multiple requests invoke the expected callback`() {
+ fun `clicking on view all when multiple requests invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setKnockRequestsBannerView(
+ setKnockRequestsBannerView(
state = aKnockRequestsBannerState(
knockRequests = listOf(
aKnockRequestPresentable(displayName = "Alice"),
@@ -60,37 +58,37 @@ class KnockRequestsBannerViewTest {
),
onViewRequestsClick = it
)
- rule.clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title)
+ clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title)
}
}
@Test
- fun `clicking on accept on a single request emit the expected event`() {
+ fun `clicking on accept on a single request emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setKnockRequestsBannerView(
+ setKnockRequestsBannerView(
state = aKnockRequestsBannerState(
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_accept)
+ clickOn(CommonStrings.action_accept)
eventsRecorder.assertSingle(KnockRequestsBannerEvents.AcceptSingleRequest)
}
@Test
- fun `clicking on dismiss emit the expected event`() {
+ fun `clicking on dismiss emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setKnockRequestsBannerView(
+ setKnockRequestsBannerView(
state = aKnockRequestsBannerState(
eventSink = eventsRecorder,
),
)
- val close = rule.activity.getString(CommonStrings.action_close)
- rule.onNodeWithContentDescription(close).performClick()
+ val close = activity!!.getString(CommonStrings.action_close)
+ onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(KnockRequestsBannerEvents.Dismiss)
}
}
-private fun AndroidComposeTestRule.setKnockRequestsBannerView(
+private fun AndroidComposeUiTest.setKnockRequestsBannerView(
state: KnockRequestsBannerState,
onViewRequestsClick: () -> Unit = EnsureNeverCalled(),
) {
diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt
index 188dcc7e56..14cac7a9b7 100644
--- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt
+++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.knockrequests.impl.list
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
@@ -23,90 +26,86 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import kotlinx.collections.immutable.persistentListOf
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class KnockRequestsListViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back invoke the expected callback`() {
+ fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setKnockRequestsListView(
+ setKnockRequestsListView(
aKnockRequestsListState(
eventSink = eventsRecorder,
),
onBackClick = it
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `clicking on accept emit the expected event`() {
+ fun `clicking on accept emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val knockRequest = aKnockRequestPresentable()
- rule.setKnockRequestsListView(
+ setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_accept)
+ clickOn(CommonStrings.action_accept)
eventsRecorder.assertSingle(KnockRequestsListEvents.Accept(knockRequest))
}
@Test
- fun `clicking on decline emit the expected event`() {
+ fun `clicking on decline emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val knockRequest = aKnockRequestPresentable()
- rule.setKnockRequestsListView(
+ setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_decline)
+ clickOn(CommonStrings.action_decline)
eventsRecorder.assertSingle(KnockRequestsListEvents.Decline(knockRequest))
}
@Test
- fun `clicking on decline and ban emit the expected event`() {
+ fun `clicking on decline and ban emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val knockRequest = aKnockRequestPresentable()
- rule.setKnockRequestsListView(
+ setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title)
+ clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title)
eventsRecorder.assertSingle(KnockRequestsListEvents.DeclineAndBan(knockRequest))
}
@Test
- fun `clicking on accept all emit the expected event`() {
+ fun `clicking on accept all emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
- rule.setKnockRequestsListView(
+ setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(knockRequests),
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_knock_requests_list_accept_all_button_title)
+ clickOn(R.string.screen_knock_requests_list_accept_all_button_title)
eventsRecorder.assertSingle(KnockRequestsListEvents.AcceptAll)
}
@Test
- fun `retry on async view retry emit the expected event`() {
+ fun `retry on async view retry emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
- rule.setKnockRequestsListView(
+ setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(knockRequests),
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
@@ -114,15 +113,15 @@ class KnockRequestsListViewTest {
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_retry)
+ clickOn(CommonStrings.action_retry)
eventsRecorder.assertSingle(KnockRequestsListEvents.RetryCurrentAction)
}
@Test
- fun `canceling async view emit the expected event`() {
+ fun `canceling async view emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
- rule.setKnockRequestsListView(
+ setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(knockRequests),
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
@@ -130,15 +129,15 @@ class KnockRequestsListViewTest {
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(KnockRequestsListEvents.ResetCurrentAction)
}
@Test
- fun `confirming async view emit the expected event`() {
+ fun `confirming async view emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
- rule.setKnockRequestsListView(
+ setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(knockRequests),
asyncAction = AsyncAction.ConfirmingNoParams,
@@ -146,12 +145,12 @@ class KnockRequestsListViewTest {
eventSink = eventsRecorder,
),
)
- rule.clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title)
+ clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title)
eventsRecorder.assertSingle(KnockRequestsListEvents.ConfirmCurrentAction)
}
}
-private fun AndroidComposeTestRule.setKnockRequestsListView(
+private fun AndroidComposeUiTest.setKnockRequestsListView(
state: KnockRequestsListState,
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
diff --git a/features/leaveroom/api/src/main/res/values-zh/translations.xml b/features/leaveroom/api/src/main/res/values-zh/translations.xml
index 6b7f17558b..a4c920a221 100644
--- a/features/leaveroom/api/src/main/res/values-zh/translations.xml
+++ b/features/leaveroom/api/src/main/res/values-zh/translations.xml
@@ -1,10 +1,10 @@
- "您确定要离开此对话吗?此对话不公开,未经邀请您将无法重新加入。"
- "确定要离开此聊天室吗?此处只有你一个人。如果离开此聊天室,包括你在内的所有人都将无法进入。"
- "确定要离开此聊天室吗?此聊天室不公开,没有邀请你将无法重新加入。"
+ "你确定要离开此对话?此对话不公开,你将无法在未经邀请的情况下重新加入。"
+ "确定要离开此房间?此处只有你一个人。如果离开,包括你在内的所有人都将无法加入。"
+ "确定要离开此房间吗?此房间不公开,没有邀请你将无法重新加入。"
"选择所有者"
- "您是本房间的唯一所有者。离开房间前,您需要将所有权转移给他人。"
+ "你是此房间的唯一所有者。离开前需要转让所有权给他人。"
"转让所有权"
- "确定要离开聊天室吗?"
+ "确定要离开房间?"
diff --git a/features/linknewdevice/impl/build.gradle.kts b/features/linknewdevice/impl/build.gradle.kts
index 9c1aa9e990..adbec91e6a 100644
--- a/features/linknewdevice/impl/build.gradle.kts
+++ b/features/linknewdevice/impl/build.gradle.kts
@@ -43,7 +43,7 @@ dependencies {
implementation(projects.libraries.permissions.api)
implementation(projects.libraries.sessionStorage.api)
implementation(projects.libraries.qrcode)
- implementation(projects.libraries.oidc.api)
+ implementation(projects.libraries.oauth.api)
implementation(projects.libraries.uiUtils)
implementation(projects.libraries.wellknown.api)
implementation(libs.androidx.browser)
@@ -56,7 +56,7 @@ dependencies {
testImplementation(projects.features.enterprise.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.matrix.test)
- testImplementation(projects.libraries.oidc.test)
+ testImplementation(projects.libraries.oauth.test)
testImplementation(projects.libraries.permissions.test)
testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.libraries.wellknown.test)
diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDeviceFlowNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDeviceFlowNode.kt
index 54baee6663..e90d318267 100644
--- a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDeviceFlowNode.kt
+++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDeviceFlowNode.kt
@@ -26,7 +26,9 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.compound.theme.ElementTheme
+import io.element.android.features.enterprise.api.SessionEnterpriseService
import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint
+import io.element.android.features.linknewdevice.impl.screens.confirmation.CodeConfirmationNode
import io.element.android.features.linknewdevice.impl.screens.desktop.DesktopNoticeNode
import io.element.android.features.linknewdevice.impl.screens.error.ErrorNode
import io.element.android.features.linknewdevice.impl.screens.error.ErrorScreenType
@@ -64,6 +66,7 @@ class LinkNewDeviceFlowNode(
private val sessionCoroutineScope: CoroutineScope,
private val linkNewMobileHandler: LinkNewMobileHandler,
private val linkNewDesktopHandler: LinkNewDesktopHandler,
+ private val sessionEnterpriseService: SessionEnterpriseService,
) : BaseFlowNode(
backstack = BackStack(
initialElement = NavTarget.Root,
@@ -107,6 +110,11 @@ class LinkNewDeviceFlowNode(
val data: String,
) : NavTarget
+ @Parcelize
+ data class CodeConfirmation(
+ val code: String,
+ ) : NavTarget
+
@Parcelize
data object MobileEnterNumber : NavTarget
@@ -145,10 +153,7 @@ class LinkNewDeviceFlowNode(
LinkMobileStep.Starting -> {
// This step is not received at the moment, so do nothing
}
- LinkMobileStep.SyncingSecrets -> {
- // LinkMobileStep.Done is not received at the moment, so consider that the flow is done here
- callback.onDone()
- }
+ LinkMobileStep.SyncingSecrets -> Unit
is LinkMobileStep.WaitingForAuth -> {
navigateToBrowser(linkMobileStep.verificationUri)
}
@@ -166,7 +171,9 @@ class LinkNewDeviceFlowNode(
is LinkDesktopStep.Error -> {
navigateToError(linkDesktopStep.errorType)
}
- is LinkDesktopStep.EstablishingSecureChannel -> Unit
+ is LinkDesktopStep.EstablishingSecureChannel -> {
+ backstack.push(NavTarget.CodeConfirmation(linkDesktopStep.checkCodeString))
+ }
is LinkDesktopStep.InvalidQrCode -> {
// This error will be handled by the ScanQrCodeNode
}
@@ -183,20 +190,20 @@ class LinkNewDeviceFlowNode(
private fun navigateToError(errorType: ErrorType) {
// Map the error to an error screen
- // TODO Update this mapping
val error = when (errorType) {
- is ErrorType.DeviceIdAlreadyInUse -> ErrorScreenType.UnknownError
- is ErrorType.InvalidCheckCode -> ErrorScreenType.InsecureChannelDetected
- is ErrorType.MissingSecretsBackup -> ErrorScreenType.UnknownError
- is ErrorType.NotFound -> ErrorScreenType.Expired
- is ErrorType.DeviceNotFound -> ErrorScreenType.UnknownError
- is ErrorType.Unknown -> ErrorScreenType.UnknownError
- is ErrorType.UnsupportedProtocol -> ErrorScreenType.UnknownError
- is ErrorType.Cancelled -> ErrorScreenType.UnknownError
+ is ErrorType.InvalidCheckCode -> ErrorScreenType.Mismatch2Digits
+ is ErrorType.UnsupportedProtocol -> ErrorScreenType.ProtocolNotSupported
+ is ErrorType.Cancelled -> ErrorScreenType.Cancelled
is ErrorType.ConnectionInsecure -> ErrorScreenType.InsecureChannelDetected
- is ErrorType.Expired -> ErrorScreenType.Expired
- is ErrorType.OtherDeviceAlreadySignedIn -> ErrorScreenType.UnknownError
+ is ErrorType.Expired,
+ is ErrorType.NotFound,
+ is ErrorType.DeviceNotFound -> ErrorScreenType.Expired
+ is ErrorType.OtherDeviceAlreadySignedIn -> ErrorScreenType.OtherDeviceAlreadySignedIn
+ // TODO check if we expect to hit this here or if it should be caught earlier on
is ErrorType.UnsupportedQrCodeType -> ErrorScreenType.UnknownError
+ is ErrorType.MissingSecretsBackup,
+ is ErrorType.DeviceIdAlreadyInUse,
+ is ErrorType.Unknown -> ErrorScreenType.UnknownError
}
// It is OK to push on backstack, since when user leaves the error screen, a new root will be set,
// or the whole flow will be popped.
@@ -250,6 +257,18 @@ class LinkNewDeviceFlowNode(
}
createNode(buildContext, listOf(callback))
}
+ is NavTarget.CodeConfirmation -> {
+ val callback = object : CodeConfirmationNode.Callback {
+ override fun onCancel() {
+ // Push error
+ backstack.push(NavTarget.Error(ErrorScreenType.Cancelled))
+ }
+ }
+ val inputs = CodeConfirmationNode.Inputs(
+ code = navTarget.code,
+ )
+ createNode(buildContext, listOf(inputs, callback))
+ }
is NavTarget.MobileShowQrCode -> {
val callback = object : ShowQrCodeNode.Callback {
override fun navigateBack() {
@@ -281,8 +300,12 @@ class LinkNewDeviceFlowNode(
}
}
- private fun navigateToBrowser(url: String) {
- activity?.openUrlInChromeCustomTab(null, darkTheme, url)
+ private suspend fun navigateToBrowser(url: String) {
+ activity?.openUrlInChromeCustomTab(
+ session = null,
+ darkTheme = darkTheme,
+ url = sessionEnterpriseService.tweakMasUrl(url),
+ )
}
@Composable
diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/confirmation/CodeConfirmationNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/confirmation/CodeConfirmationNode.kt
new file mode 100644
index 0000000000..a8db4d2d75
--- /dev/null
+++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/confirmation/CodeConfirmationNode.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.features.linknewdevice.impl.screens.confirmation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import dev.zacsweers.metro.Assisted
+import dev.zacsweers.metro.AssistedInject
+import io.element.android.annotations.ContributesNode
+import io.element.android.libraries.architecture.NodeInputs
+import io.element.android.libraries.architecture.callback
+import io.element.android.libraries.architecture.inputs
+import io.element.android.libraries.di.SessionScope
+
+@ContributesNode(SessionScope::class)
+@AssistedInject
+class CodeConfirmationNode(
+ @Assisted buildContext: BuildContext,
+ @Assisted plugins: List,
+) : Node(buildContext = buildContext, plugins = plugins) {
+ interface Callback : Plugin {
+ fun onCancel()
+ }
+
+ data class Inputs(
+ val code: String,
+ ) : NodeInputs
+
+ private val callback: Callback = callback()
+ private val input = inputs()
+
+ @Composable
+ override fun View(modifier: Modifier) {
+ CodeConfirmationView(
+ code = input.code,
+ onCancel = callback::onCancel,
+ )
+ }
+}
diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/confirmation/CodeConfirmationView.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/confirmation/CodeConfirmationView.kt
new file mode 100644
index 0000000000..d981574f86
--- /dev/null
+++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/confirmation/CodeConfirmationView.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.features.linknewdevice.impl.screens.confirmation
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import io.element.android.compound.theme.ElementTheme
+import io.element.android.compound.tokens.generated.CompoundIcons
+import io.element.android.features.linknewdevice.impl.R
+import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
+import io.element.android.libraries.designsystem.components.BigIcon
+import io.element.android.libraries.designsystem.preview.ElementPreview
+import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
+import io.element.android.libraries.designsystem.theme.components.OutlinedButton
+import io.element.android.libraries.designsystem.theme.components.Text
+import io.element.android.libraries.ui.strings.CommonStrings
+
+@Composable
+fun CodeConfirmationView(
+ code: String,
+ onCancel: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ BackHandler(onBack = onCancel)
+ FlowStepPage(
+ modifier = modifier,
+ iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()),
+ title = stringResource(R.string.screen_qr_code_login_device_code_title),
+ subTitle = stringResource(R.string.screen_qr_code_login_device_code_subtitle),
+ content = { Content(code = code) },
+ buttons = { Buttons(onCancel = onCancel) }
+ )
+}
+
+@Composable
+private fun Content(code: String) {
+ Column(
+ modifier = Modifier.padding(top = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Digits(code = code)
+ Spacer(modifier = Modifier.height(32.dp))
+ WaitingForOtherDevice()
+ }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+private fun Digits(code: String) {
+ FlowRow(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ code.forEach {
+ Text(
+ modifier = Modifier
+ .padding(horizontal = 6.dp, vertical = 4.dp)
+ .clip(RoundedCornerShape(4.dp))
+ .background(ElementTheme.colors.bgActionSecondaryPressed)
+ .padding(horizontal = 16.dp, vertical = 17.dp),
+ text = it.toString()
+ )
+ }
+ }
+}
+
+@Composable
+private fun WaitingForOtherDevice() {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .size(20.dp)
+ .padding(2.dp),
+ strokeWidth = 2.dp,
+ )
+ Text(
+ text = stringResource(R.string.screen_qr_code_login_verify_code_loading),
+ style = ElementTheme.typography.fontBodySmRegular,
+ color = ElementTheme.colors.textSecondary,
+ textAlign = TextAlign.Center,
+ )
+ }
+}
+
+@Composable
+private fun Buttons(
+ onCancel: () -> Unit,
+) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ OutlinedButton(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(CommonStrings.action_cancel),
+ onClick = onCancel,
+ )
+ }
+}
+
+@PreviewsDayNight
+@Composable
+internal fun CodeConfirmationViewPreview() {
+ ElementPreview {
+ CodeConfirmationView(
+ code = "67",
+ onCancel = {},
+ )
+ }
+}
diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenType.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenType.kt
index b92a19ef8a..ad8cc276c5 100644
--- a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenType.kt
+++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenType.kt
@@ -20,6 +20,9 @@ sealed interface ErrorScreenType : NodeInputs, Parcelable {
@Parcelize
data object Expired : ErrorScreenType
+ @Parcelize
+ data object OtherDeviceAlreadySignedIn : ErrorScreenType
+
@Parcelize
data object Mismatch2Digits : ErrorScreenType
diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenTypeProvider.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenTypeProvider.kt
index 7fd699101b..5946eb9ab2 100644
--- a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenTypeProvider.kt
+++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenTypeProvider.kt
@@ -19,5 +19,6 @@ class ErrorScreenTypeProvider : PreviewParameterProvider {
ErrorScreenType.InsecureChannelDetected,
ErrorScreenType.SlidingSyncNotAvailable,
ErrorScreenType.UnknownError,
+ ErrorScreenType.OtherDeviceAlreadySignedIn,
)
}
diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorView.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorView.kt
index 9f67e8bc17..4db2aa9ad5 100644
--- a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorView.kt
+++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorView.kt
@@ -47,17 +47,26 @@ fun ErrorView(
) {
val appName = LocalBuildMeta.current.applicationName
BackHandler(onBack = onCancel)
+ val iconStyle = when (errorScreenType) {
+ ErrorScreenType.OtherDeviceAlreadySignedIn -> BigIcon.Style.SuccessSolid
+ else -> BigIcon.Style.AlertSolid
+ }
FlowStepPage(
modifier = modifier,
- iconStyle = BigIcon.Style.AlertSolid,
+ iconStyle = iconStyle,
title = titleText(errorScreenType, appName),
subTitle = subtitleText(errorScreenType, appName),
content = { Content(errorScreenType) },
buttons = {
- Buttons(
- onRetry = onRetry,
- onCancel = onCancel,
- )
+ when (errorScreenType) {
+ ErrorScreenType.OtherDeviceAlreadySignedIn -> DoneButton(
+ onDone = onCancel,
+ )
+ else -> Buttons(
+ onRetry = onRetry,
+ onCancel = onCancel,
+ )
+ }
},
)
}
@@ -72,6 +81,7 @@ private fun titleText(errorScreenType: ErrorScreenType, appName: String) = when
ErrorScreenType.Mismatch2Digits -> stringResource(id = R.string.screen_link_new_device_wrong_number_title)
ErrorScreenType.SlidingSyncNotAvailable -> stringResource(id = R.string.screen_qr_code_login_error_sliding_sync_not_supported_title, appName)
is ErrorScreenType.UnknownError -> stringResource(CommonStrings.common_something_went_wrong)
+ ErrorScreenType.OtherDeviceAlreadySignedIn -> stringResource(R.string.screen_qr_code_login_error_device_already_signed_in_title)
}
@Composable
@@ -84,6 +94,7 @@ private fun subtitleText(errorScreenType: ErrorScreenType, appName: String) = wh
ErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_description)
ErrorScreenType.SlidingSyncNotAvailable -> stringResource(id = R.string.screen_qr_code_login_error_sliding_sync_not_supported_subtitle, appName)
is ErrorScreenType.UnknownError -> stringResource(R.string.screen_qr_code_login_unknown_error_description)
+ ErrorScreenType.OtherDeviceAlreadySignedIn -> stringResource(R.string.screen_qr_code_login_error_device_already_signed_in_subtitle)
}
@Composable
@@ -124,6 +135,17 @@ private fun Content(errorScreenType: ErrorScreenType) {
}
}
+@Composable
+private fun DoneButton(
+ onDone: () -> Unit,
+) {
+ Button(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(CommonStrings.action_done),
+ onClick = onDone,
+ )
+}
+
@Composable
private fun Buttons(
onRetry: () -> Unit,
diff --git a/features/linknewdevice/impl/src/main/res/values-be/translations.xml b/features/linknewdevice/impl/src/main/res/values-be/translations.xml
index 16372fa6e4..378a405c95 100644
--- a/features/linknewdevice/impl/src/main/res/values-be/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-be/translations.xml
@@ -17,6 +17,8 @@
"Калі вы сутыкнуліся з той жа праблемай, паспрабуйце іншую сетку Wi-Fi або скарыстайцеся мабільнымі дадзенымі замест Wi-Fi."
"Калі гэта не дапамагло, увайдзіце ўручную"
"Злучэнне небяспечнае"
+ "Вам будзе прапанавана ўвесці дзве лічбы, паказаныя на гэтай прыладзе."
+ "Увядзіце наступны нумар на іншай прыладзе."
"Уваход быў адменены на іншай прыладзе."
"Запыт на ўваход скасаваны"
"Уваход на іншай прыладзе быў адхілены."
@@ -35,4 +37,5 @@
"Каб працягнуць, вам неабходна дазволіць %1$s выкарыстоўваць камеру вашай прылады."
"Дазвольце доступ да камеры для сканіравання QR-кода"
"Адбылася нечаканая памылка. Калі ласка, паспрабуйце яшчэ раз."
+ "У чаканні іншай прылады"
diff --git a/features/linknewdevice/impl/src/main/res/values-cs/translations.xml b/features/linknewdevice/impl/src/main/res/values-cs/translations.xml
index 4b8f230d55..e0150668a3 100644
--- a/features/linknewdevice/impl/src/main/res/values-cs/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-cs/translations.xml
@@ -34,6 +34,8 @@
"Pokud narazíte na stejný problém, zkuste jinou síť wifi nebo použijte mobilní data místo wifi"
"Pokud to nefunguje, přihlaste se ručně"
"Připojení není zabezpečené"
+ "Budete požádáni o zadání dvou níže uvedených číslic."
+ "Zadejte níže uvedené číslo na svém dalším zařízení"
"Přihlášení bylo na druhém zařízení zrušeno."
"Žádost o přihlášení zrušena"
"Přihlášení bylo na druhém zařízení odmítnuto."
@@ -54,4 +56,5 @@ Zkuste se přihlásit ručně nebo naskenujte QR kód pomocí jiného zařízen
"Abyste mohli pokračovat, musíte aplikaci %1$s udělit povolení k použití kamery vašeho zařízení."
"Povolte přístup k fotoaparátu a naskenujte QR kód"
"Vyskytla se neočekávaná chyba. Prosím zkuste to znovu."
+ "Čekání na vaše další zařízení"
diff --git a/features/linknewdevice/impl/src/main/res/values-cy/translations.xml b/features/linknewdevice/impl/src/main/res/values-cy/translations.xml
index b26aed52ef..6b1cb7781f 100644
--- a/features/linknewdevice/impl/src/main/res/values-cy/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-cy/translations.xml
@@ -17,6 +17,8 @@
"Os ydych chi\'n dod ar draws yr un broblem, rhowch gynnig ar rwydwaith wifi gwahanol neu defnyddiwch eich data symudol yn lle wifi"
"Os nad yw hynny\'n gweithio, mewngofnodwch â llaw"
"Nid yw\'r cysylltiad yn ddiogel"
+ "Bydd gofyn i chi nodi\'r ddau ddigid sy\'n cael eu dangos ar y ddyfais hon."
+ "Rhowch y rhif isod ar eich dyfais arall"
"Cafodd y mewngofnodi ei ddiddymu ar y ddyfais arall."
"Cais mewngofnodi wedi\'i ddiddymu"
"Cafodd y mewngofnodi ar y ddyfais arall ei wrthod."
@@ -35,4 +37,5 @@ Ceisiwch fewngofnodi â llaw, neu sganiwch y cod QR gyda dyfais arall."
"Mae angen i chi roi caniatâd i %1$s ddefnyddio camera eich dyfais er mwyn parhau."
"Caniatáu mynediad camera i sganio\'r cod QR"
"Digwyddodd gwall annisgwyl. Ceisiwch eto."
+ "Yn aros am eich dyfais arall"
diff --git a/features/linknewdevice/impl/src/main/res/values-da/translations.xml b/features/linknewdevice/impl/src/main/res/values-da/translations.xml
index 5bc9f04fb9..45f510e90b 100644
--- a/features/linknewdevice/impl/src/main/res/values-da/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-da/translations.xml
@@ -34,6 +34,8 @@
"Hvis du støder på det samme problem, kan du prøve et andet wifi-netværk eller bruge dine mobildata i stedet for wifi"
"Hvis det ikke virker, skal du logge ind manuelt"
"Forbindelsen er ikke sikker"
+ "Du bliver bedt om at indtaste de to cifre, der vises på denne enhed."
+ "Indtast nummeret herunder på din anden enhed"
"Login blev annulleret på den anden enhed."
"Anmodning om login annulleret"
"Login blev afvist på den anden enhed."
@@ -54,4 +56,5 @@ Prøv at logge ind manuelt, eller scan QR-koden med en anden enhed."
"Du skal give tilladelse til at %1$s kan benytte enhedens kamera, for at fortsætte."
"Tillad kameraadgang for at scanne QR-koden"
"Der opstod en uventet fejl. Prøv venligst igen."
+ "Venter på din anden enhed"
diff --git a/features/linknewdevice/impl/src/main/res/values-de/translations.xml b/features/linknewdevice/impl/src/main/res/values-de/translations.xml
index b8ad8b80ef..773878c736 100644
--- a/features/linknewdevice/impl/src/main/res/values-de/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-de/translations.xml
@@ -34,6 +34,8 @@
"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN."
"Wenn das nicht funktioniert, melde dich manuell an"
"Die Verbindung ist nicht sicher"
+ "Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."
+ "Trage die unten angezeigte Zahl auf einem anderen Device ein"
"Die Anmeldung wurde auf dem anderen Gerät abgebrochen."
"Anmeldeanfrage abgebrochen"
"Die Anmeldung auf dem anderen Gerät wurde abgelehnt."
@@ -54,4 +56,5 @@ Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Ger
"Du musst %1$s die Berechtigung erteilen, die Kamera deines Geräts zu verwenden, um fortzufahren."
"Erlaube Zugriff auf die Kamera zum Scannen des QR-Codes"
"Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."
+ "Warten auf dein anderes Gerät"
diff --git a/features/linknewdevice/impl/src/main/res/values-el/translations.xml b/features/linknewdevice/impl/src/main/res/values-el/translations.xml
index 26c917075b..6c0e77da40 100644
--- a/features/linknewdevice/impl/src/main/res/values-el/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-el/translations.xml
@@ -34,6 +34,8 @@
"Εάν αντιμετωπίσεις το ίδιο πρόβλημα, δοκίμασε ένα διαφορετικό δίκτυο wifi ή χρησιμοποίησε τα δεδομένα του κινητού σου αντί για wifi"
"Εάν δεν λειτουργήσει, συνδέσου χειροκίνητα"
"Η σύνδεση δεν είναι ασφαλής"
+ "Θα σου ζητηθεί να εισάγεις τα δύο ψηφία που εμφανίζονται σε αυτήν τη συσκευή."
+ "Εισήγαγε τον παρακάτω αριθμό στην άλλη συσκευή σου"
"Η σύνδεση ακυρώθηκε στην άλλη συσκευή."
"Το αίτημα σύνδεσης ακυρώθηκε"
"Η σύνδεση απορρίφθηκε στην άλλη συσκευή."
@@ -54,4 +56,5 @@
"Πρέπει να δώσεις άδεια για %1$s για να χρησιμοποιήσεις την κάμερα της συσκευής σου και να συνεχίσεις."
"Επέτρεψε την πρόσβαση της κάμερας για σάρωση του κωδικού QR"
"Παρουσιάστηκε ένα απροσδόκητο σφάλμα. Παρακαλώ προσπάθησε ξανά."
+ "Αναμονή για την άλλη σου συσκευή"
diff --git a/features/linknewdevice/impl/src/main/res/values-es/translations.xml b/features/linknewdevice/impl/src/main/res/values-es/translations.xml
index 032813a2c4..c33dc3ad88 100644
--- a/features/linknewdevice/impl/src/main/res/values-es/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-es/translations.xml
@@ -17,6 +17,8 @@
"Si te encuentras con el mismo problema, prueba con una red wifi diferente o usa tus datos móviles en lugar de wifi"
"Si eso no funciona, inicia sesión manualmente"
"La conexión no es segura"
+ "Se te pedirá que introduzcas los dos dígitos mostrados en este dispositivo."
+ "Introduce el número que aparece a continuación en tu otro dispositivo"
"El inicio de sesión se canceló en el otro dispositivo."
"Solicitud de inicio de sesión cancelada"
"El inicio de sesión se rechazó en el otro dispositivo."
@@ -35,4 +37,5 @@ Intenta iniciar sesión manualmente o escanea el código QR con otro dispositivo
"Tienes que dar permiso a %1$s para que utilice la cámara de tu dispositivo y así poder continuar."
"Permite el acceso a la cámara para escanear el código QR"
"Se ha producido un error inesperado. Vuelve a intentarlo."
+ "A la espera de tu otro dispositivo"
diff --git a/features/linknewdevice/impl/src/main/res/values-et/translations.xml b/features/linknewdevice/impl/src/main/res/values-et/translations.xml
index 6aa1398e0a..10f8af5e11 100644
--- a/features/linknewdevice/impl/src/main/res/values-et/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-et/translations.xml
@@ -34,6 +34,8 @@
"Kui sama probleem kordub, siis kasuta mõnda muud WiFi- või mobiilset andmedsideühendust"
"Kui see ka ei aita, siis logi sisse käsitsi"
"Ühendus pole turvaline"
+ "Sul palutakse sisestada kaks selles seadmes kuvatud numbrit."
+ "Sisesta see number oma teises seadmes"
"Sisselogimine katkestati teises seadmes."
"Sisselogimispäring on tühistatud"
"Sisselogimisest on teises seadmes keeldutud."
@@ -54,4 +56,5 @@ Proovi käsitsi sisselogimist või skaneeri QR-koodi mõne muu seadmega.""Jätkamiseks pead lubama, et %1$s saab kasutada sinu nutiseadme kaamerat"
"QR-koodi lugemiseks luba kaamerat kasutada"
"Tekkis ootamatu viga. Palun proovi uuesti."
+ "Ootame sinu teise seadme järgi"
diff --git a/features/linknewdevice/impl/src/main/res/values-eu/translations.xml b/features/linknewdevice/impl/src/main/res/values-eu/translations.xml
index 06cc0fd857..8680ad94c7 100644
--- a/features/linknewdevice/impl/src/main/res/values-eu/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-eu/translations.xml
@@ -16,6 +16,8 @@
"Saiatu berriro QR kodearekin saioa hasten sare-arazo bat izan bada"
"Horrek ez badu funtzionatzen, hasi saioa eskuz"
"Konexioa ez da segurua"
+ "Gailu honetan agertzen diren bi digituak sartzeko eskatuko zaizu."
+ "Sartu beheko zenbakia beste gailuan"
"Saioa hasteko eskaera bertan behera utzi da beste gailuan"
"Saioa hasteko eskaera bertan behera utzi da"
"Saioa hasteari uko egin zaio beste dispositiboan."
@@ -33,4 +35,5 @@ Saiatu saioa eskuz hasten, edo eskaneatu QR kodea beste gailu batean."
"QR kode okerra"
"Baimendu kameraren sarbidea QR kodea eskaneatzeko"
"Ustekabeko errore bat gertatu da. Saiatu berriro."
+ "Beste gailuaren zain"
diff --git a/features/linknewdevice/impl/src/main/res/values-fa/translations.xml b/features/linknewdevice/impl/src/main/res/values-fa/translations.xml
index 804fa653ad..07c329ef6a 100644
--- a/features/linknewdevice/impl/src/main/res/values-fa/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-fa/translations.xml
@@ -15,6 +15,8 @@
"اکنون چه؟"
"ورود دستی در صورت کار نکردنش"
"اتّصال ناامن"
+ "از شما خواسته خواهد شد که دو رقم نشان داده روی این افزاره را وارد کنید."
+ "شمارهٔ زیر را روی افزارهٔ دیگرتان وارد کنید"
"ورود روی افزارهٔ دیگر لغو شد."
"درخواست ورد لغو شد"
"ورود به دست افزارهٔ دیگر رد شد."
@@ -33,4 +35,5 @@
"برای ادامه باید اجازهٔ استفادهٔ %1$s از دوربین افزارهتان را بدهید."
"اجازهٔ دسترسی دوربین برای پویش کد پاس"
"خطایی غیرمنتظره رخ داد. لطفاً دوباره تلاش کنید."
+ "منتظر افزارهٔ دیگرتان"
diff --git a/features/linknewdevice/impl/src/main/res/values-fi/translations.xml b/features/linknewdevice/impl/src/main/res/values-fi/translations.xml
index f8e999f886..0ba5a30e58 100644
--- a/features/linknewdevice/impl/src/main/res/values-fi/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-fi/translations.xml
@@ -34,6 +34,8 @@
"Jos kohtaat saman ongelman, kokeile toista wifi-verkkoa tai käytä mobiilidataa wifi-yhteyden sijaan"
"Jos tämä ei auta, kirjaudu sisään manuaalisesti"
"Yhteys ei ole turvallinen"
+ "Sinua pyydetään antamaan tässä laitteessa näkyvät kaksi numeroa."
+ "Kirjoita alla oleva numero toisella laitteellasi"
"Kirjautuminen peruutettiin toisella laitteella."
"Kirjautumispyyntö peruutettu"
"Kirjautuminen hylättiin toisella laitteella."
@@ -54,4 +56,5 @@ Yritä kirjautua sisään manuaalisesti tai skannaa QR-koodi toisella laitteella
"Jatkaaksesi sinun on annettava lupa %1$s -sovellukselle käyttää laitteesi kameraa."
"Salli lupa kameraan QR-koodin skannaamiseksi"
"Tapahtui odottamaton virhe. Yritä uudelleen."
+ "Odotetaan toista laitettasi"
diff --git a/features/linknewdevice/impl/src/main/res/values-fr/translations.xml b/features/linknewdevice/impl/src/main/res/values-fr/translations.xml
index 0c91dca7a1..12a770af17 100644
--- a/features/linknewdevice/impl/src/main/res/values-fr/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-fr/translations.xml
@@ -34,6 +34,8 @@
"Si vous rencontrez le même problème, essayez un autre réseau wifi ou utilisez vos données mobiles au lieu du wifi"
"Si cela ne fonctionne pas, connectez-vous manuellement"
"La connexion n’est pas sécurisée"
+ "Il vous sera demandé de saisir les deux chiffres affichés sur cet appareil."
+ "Saisissez le nombre ci-dessous sur votre autre appareil"
"La connexion a été annulée sur l’autre appareil."
"Demande de connexion annulée"
"La connexion a été refusée sur l’autre appareil."
@@ -52,4 +54,5 @@
"Vous devez autoriser %1$s à utiliser la camera de votre appareil pour continuer."
"Autoriser l’usage de la caméra pour scanner le code QR"
"Une erreur inattendue s’est produite. Veuillez réessayer."
+ "En attente de votre autre session"
diff --git a/features/linknewdevice/impl/src/main/res/values-hr/translations.xml b/features/linknewdevice/impl/src/main/res/values-hr/translations.xml
index 20c194ef93..8561e63ad3 100644
--- a/features/linknewdevice/impl/src/main/res/values-hr/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-hr/translations.xml
@@ -34,6 +34,8 @@
"Ako se problem ponovi, pokušajte s drugom Wi-Fi mrežom ili mobilnim podatcima umjesto Wi-Fi-ja."
"Ako to ne uspije, prijavite se ručno"
"Veza nije sigurna"
+ "Od vas će se zatražiti da unesete dvije znamenke prikazane na ovom uređaju."
+ "Unesite ispod navedeni broj u svoj drugi uređaj"
"Prijava je otkazana na drugom uređaju."
"Zahtjev za prijavu je otkazan"
"Prijava je odbijena na drugom uređaju."
@@ -54,4 +56,5 @@ Pokušajte se prijaviti ručno ili skenirajte QR kod drugim uređajem."
"Za nastavak morate dati dopuštenje za %1$s da biste se mogli služiti kamerom svog uređaja."
"Dopustite pristup kameri kako biste mogli skenirati QR kod"
"Došlo je do neočekivane pogreške. Pokušajte ponovno."
+ "Čekanje na vaš drugi uređaj"
diff --git a/features/linknewdevice/impl/src/main/res/values-hu/translations.xml b/features/linknewdevice/impl/src/main/res/values-hu/translations.xml
index 51fe30bbd8..8cefed94cc 100644
--- a/features/linknewdevice/impl/src/main/res/values-hu/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-hu/translations.xml
@@ -34,6 +34,8 @@
"Ha ugyanezzel a problémával találkozik, próbálkozzon másik Wi-Fi-hálózattal, vagy a Wi-Fi helyett használja a mobil-adatkapcsolatát"
"Ha ez nem működik, jelentkezzen be kézileg"
"A kapcsolat nem biztonságos"
+ "A rendszer kérni fogja, hogy adja meg az alábbi két számjegyet az eszközén."
+ "Adja meg az alábbi számot a másik eszközén"
"A bejelentkezést megszakították a másik eszközön."
"Bejelentkezési kérés törölve"
"A bejelentkezést elutasították a másik eszközön."
@@ -54,4 +56,5 @@ Próbáljon meg kézileg bejelentkezni, vagy olvassa be a QR-kódot egy másik e
"A folytatáshoz engedélyeznie kell, hogy az %1$s használhassa az eszköz kameráját."
"Engedélyezze a kamera elérését a QR-kód beolvasásához"
"Váratlan hiba történt. Próbálja meg újra."
+ "Várakozás a másik eszközre"
diff --git a/features/linknewdevice/impl/src/main/res/values-in/translations.xml b/features/linknewdevice/impl/src/main/res/values-in/translations.xml
index 20badba9ba..5508993090 100644
--- a/features/linknewdevice/impl/src/main/res/values-in/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-in/translations.xml
@@ -17,6 +17,8 @@
"Jika Anda mengalami masalah yang sama, coba jaringan Wi-Fi yang berbeda atau gunakan data seluler Anda daripada Wi-Fi"
"Jika tidak berhasil, masuk secara manual"
"Koneksi tidak aman"
+ "Anda akan diminta untuk memasukkan dua digit yang ditunjukkan di perangkat ini."
+ "Masukkan nomor bawah di perangkat Anda yang lain"
"Proses masuk dibatalkan di perangkat lain."
"Permintaan masuk dibatalkan"
"Proses masuk ditolak di perangkat lain."
@@ -35,4 +37,5 @@ Coba masuk secara manual, atau pindai kode QR dengan perangkat lain."
"Anda perlu memberikan izin ke %1$s untuk menggunakan kamera perangkat Anda untuk melanjutkan."
"Izinkan akses kamera untuk memindai kode QR"
"Terjadi kesalahan tak terduga. Silakan coba lagi."
+ "Menunggu perangkat Anda yang lain"
diff --git a/features/linknewdevice/impl/src/main/res/values-it/translations.xml b/features/linknewdevice/impl/src/main/res/values-it/translations.xml
index 9a6476823a..6a32c70fe4 100644
--- a/features/linknewdevice/impl/src/main/res/values-it/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-it/translations.xml
@@ -34,6 +34,8 @@
"Se riscontri lo stesso problema, prova con un altra rete wifi o usa i dati mobili al posto del wifi."
"Se il problema persiste, accedi manualmente"
"La connessione non è sicura"
+ "Ti verrà chiesto di inserire le due cifre mostrate su questo dispositivo."
+ "Inserisci il numero qui sotto sull\'altro dispositivo"
"L\'accesso è stato annullato sull\'altro dispositivo."
"Richiesta di accesso annullata"
"L\'accesso è stato rifiutato sull\'altro dispositivo."
@@ -54,4 +56,5 @@ Prova ad accedere manualmente o scansiona il codice QR con un altro dispositivo.
"Per continuare, è necessario fornire l\'autorizzazione a %1$s per utilizzare la fotocamera del dispositivo."
"Consenti l\'accesso alla fotocamera per la scansione del codice QR"
"Si è verificato un errore inatteso. Riprova."
+ "In attesa dell\'altro dispositivo"
diff --git a/features/linknewdevice/impl/src/main/res/values-ja/translations.xml b/features/linknewdevice/impl/src/main/res/values-ja/translations.xml
index 6cfd5baf84..c4f09dcd0e 100644
--- a/features/linknewdevice/impl/src/main/res/values-ja/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-ja/translations.xml
@@ -18,7 +18,7 @@
"サインインが無効です。もう一度試してください。"
"サインインが時間内に完了しませんでした"
"%1$s を他の端末で開いてください"
- "%1$s を選択してください"
+ "%1$s を選択"
"\"QRコードでサインイン\""
"表示されているQRコードを一方の端末で読み取ってください"
"%1$s を他の端末で開いてください"
@@ -34,6 +34,8 @@
"同様の問題が発生する場合は、異なるWi-Fiやモバイルデータ通信を試してください"
"問題が解決しない場合は、手動でサインインしてください"
"接続が安全ではありません"
+ "この端末に表示される2つの数字の入力を要求されます"
+ "もう一方に表示される数字を入力してください"
"もう一方の端末がサインインをキャンセルしました"
"サインインのリクエストがキャンセルされました"
"もう一方の端末でサインインを拒否されました"
@@ -54,4 +56,5 @@
"続行するには、%1$s にカメラの使用を許可する必要があります。"
"QRコードを読み取るため、カメラへのアクセスを許可"
"予期せぬ問題が発生しました。もう一度試してください。"
+ "一方の端末を待機しています"
diff --git a/features/linknewdevice/impl/src/main/res/values-ko/translations.xml b/features/linknewdevice/impl/src/main/res/values-ko/translations.xml
index 3b31c8fdc2..1d69f58ca5 100644
--- a/features/linknewdevice/impl/src/main/res/values-ko/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-ko/translations.xml
@@ -34,6 +34,8 @@
"동일한 문제를 겪으신 경우 다른 Wi-Fi 네트워크를 사용해 보거나 Wi-Fi 대신 모바일 데이터를 사용해 보세요."
"만약 작동하지 않는 경우, 수동으로 로그인하세요."
"연결이 안전하지 않습니다"
+ "이 장치에 표시된 두 자리 숫자를 입력하라는 메시지가 표시됩니다."
+ "다른 device 에 아래 번호를 입력하세요"
"다른 기기에서 로그인이 취소되었습니다."
"로그인 요청이 취소되었습니다"
"다른 기기에서 로그인이 거부되었습니다."
@@ -54,4 +56,5 @@
"계속하려면 %1$s 가 기기의 카메라를 사용할 수 있도록 권한을 부여해야 합니다."
"카메라 액세스를 허용하여 QR 코드를 스캔하세요"
"예기치 않은 오류가 발생했습니다. 다시 시도해 주세요."
+ "다른 기기를 기다리고 있습니다"
diff --git a/features/linknewdevice/impl/src/main/res/values-nb/translations.xml b/features/linknewdevice/impl/src/main/res/values-nb/translations.xml
index 6b8541b25b..4e8844ee2f 100644
--- a/features/linknewdevice/impl/src/main/res/values-nb/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-nb/translations.xml
@@ -34,6 +34,8 @@
"Hvis du støter på det samme problemet, kan du prøve et annet wifi-nettverk eller bruke mobildata i stedet for wifi"
"Hvis det ikke fungerer, kan du logge på manuelt"
"Forbindelsen er ikke sikker"
+ "Du blir bedt om å skrive inn de to sifrene som vises på denne enheten."
+ "Skriv inn nummeret nedenfor på den andre enheten"
"Påloggingen ble kansellert på den andre enheten."
"Påloggingsforespørsel kansellert"
"Påloggingen ble avvist på den andre enheten."
@@ -54,4 +56,5 @@ Prøv å logge på manuelt, eller skann QR-koden med en annen enhet."
"Du må gi tillatelse til at %1$s kan bruke enhetens kamera for å fortsette."
"Tillat kameratilgang for å skanne QR-koden"
"Det oppstod en uventet feil. Prøv igjen."
+ "Venter på den andre enheten din"
diff --git a/features/linknewdevice/impl/src/main/res/values-nl/translations.xml b/features/linknewdevice/impl/src/main/res/values-nl/translations.xml
index 407a470e48..6dbc2c18c2 100644
--- a/features/linknewdevice/impl/src/main/res/values-nl/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-nl/translations.xml
@@ -17,6 +17,8 @@
"Als je hetzelfde probleem ondervindt, probeer dan een ander wifi-netwerk of gebruik je mobiele data in plaats van wifi."
"Als dat niet werkt, log dan handmatig in"
"Verbinding niet veilig"
+ "Daar word je gevraagd om de twee cijfers in te voeren die op dit apparaat worden weergegeven."
+ "Voer het onderstaande nummer in op je andere apparaat"
"De aanmelding is geannuleerd op het andere apparaat."
"Login verzoek geannuleerd"
"De aanmelding is geweigerd op het andere apparaat."
@@ -35,4 +37,5 @@ Probeer handmatig in te loggen, of scan de QR code met een ander apparaat.""Je moet %1$s toestemming geven om de camera van je apparaat te gebruiken om verder te gaan."
"Cameratoegang toestaan om de QR-code te scannen"
"Er is een onverwachte fout opgetreden. Probeer het opnieuw."
+ "Aan het wachten op je andere apparaat"
diff --git a/features/linknewdevice/impl/src/main/res/values-pl/translations.xml b/features/linknewdevice/impl/src/main/res/values-pl/translations.xml
index 4db42a2a49..18b731a528 100644
--- a/features/linknewdevice/impl/src/main/res/values-pl/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-pl/translations.xml
@@ -17,6 +17,8 @@
"Jeśli napotkasz ten sam problem, użyj innej sieci Wi-FI lub danych mobilnych"
"Jeśli to nie zadziała, zaloguj się ręcznie"
"Połączenie nie jest bezpieczne"
+ "Zostaniesz poproszony o wprowadzenie dwóch cyfr widocznych na tym urządzeniu."
+ "Wprowadź numer poniżej na innym urządzeniu"
"Logowanie zostało anulowane na drugim urządzeniu."
"Prośba o logowanie została anulowana"
"Logowanie zostało odrzucone na drugim urządzeniu."
@@ -35,4 +37,5 @@ Spróbuj zalogować się ręcznie lub zeskanuj kod QR na innym urządzeniu.""Musisz przyznać uprawnienia %1$s do korzystania z kamery, aby kontynuować."
"Zezwól na dostęp do kamery, aby zeskanować kod QR"
"Wystąpił nieoczekiwany błąd. Spróbuj ponownie."
+ "Oczekiwanie na drugie urządzenie"
diff --git a/features/linknewdevice/impl/src/main/res/values-pt-rBR/translations.xml b/features/linknewdevice/impl/src/main/res/values-pt-rBR/translations.xml
index f11bdc6e6d..1680e5c0ff 100644
--- a/features/linknewdevice/impl/src/main/res/values-pt-rBR/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-pt-rBR/translations.xml
@@ -34,6 +34,8 @@
"Se o problema persistir, tente uma rede Wi-Fi diferente ou use seus dados móveis em vez de Wi-Fi"
"Se isso não funcionar, entre manualmente"
"Conexão insegura"
+ "Você será solicitado a inserir os dois dígitos mostrados neste dispositivo."
+ "Digite o número abaixo no seu outro dispositivo"
"A entrada foi cancelada no outro dispositivo."
"Solicitação de entrada foi cancelada"
"A entrada foi recusada no outro dispositivo."
@@ -54,4 +56,5 @@ Tente entrar manualmente ou ler o código QR com outro dispositivo."
"Você deve permitir que o %1$s use a câmera do seu dispositivo para continuar."
"Permita o acesso à câmera para ler o código QR"
"Ocorreu um erro inesperado. Tente novamente."
+ "Aguardando seu outro dispositivo"
diff --git a/features/linknewdevice/impl/src/main/res/values-pt/translations.xml b/features/linknewdevice/impl/src/main/res/values-pt/translations.xml
index da6da08f38..eff27b17f0 100644
--- a/features/linknewdevice/impl/src/main/res/values-pt/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-pt/translations.xml
@@ -17,6 +17,8 @@
"Se tiveres o mesmo problema, experimenta uma rede Wi-Fi diferente ou utiliza os teus dados móveis."
"Se isso não funcionar, inicia sessão manualmente"
"Ligação insegura"
+ "Ser-te-á pedido que insiras os dois dígitos indicados neste dispositivo."
+ "Insere o número abaixo no teu dispositivo"
"O início de sessão foi cancelado no outro dispositivo."
"Pedido de início de sessão cancelado"
"O início de sessão foi rejeitado no outro dispositivo."
@@ -35,4 +37,5 @@ Tenta iniciar a sessão manualmente ou digitaliza o código QR com outro disposi
"Para continuar, tens que dar permissão à %1$s para aceder à câmara do teu dispositivo."
"Permitir o acesso à câmara para ler o código QR"
"Ocorreu um erro inesperado. Tenta novamente."
+ "À espera do teu outro dispositivo"
diff --git a/features/linknewdevice/impl/src/main/res/values-ro/translations.xml b/features/linknewdevice/impl/src/main/res/values-ro/translations.xml
index f1a4f3db59..c99b537084 100644
--- a/features/linknewdevice/impl/src/main/res/values-ro/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-ro/translations.xml
@@ -33,6 +33,8 @@
"Dacă întâmpinați aceeași problemă, încercați o altă rețea Wi-Fi sau utilizați datele mobile în loc de Wi-Fi."
"Dacă nu funcționează, conectați-vă manual"
"Conexiunea nu este sigură"
+ "Vi se va cere să introduceți cele două cifre afișate pe acest dispozitiv."
+ "Introduceți numărul de mai jos pe celălalt dispozitiv"
"Autentificarea a fost anulată de pe celălalt dispozitiv."
"Cererea de autentificare a fost anulată"
"Autentificarea a fost refuzată pe celălalt dispozitiv."
@@ -51,4 +53,5 @@
"Trebuie să acordați permisiunea ca %1$s să folosească camera dispozitivului pentru a continua."
"Permiteți accesul la cameră pentru a scana codul QR"
"A apărut o eroare neașteptată. Vă rugăm să încercați din nou."
+ "În așteptarea celuilalt dispozitiv"
diff --git a/features/linknewdevice/impl/src/main/res/values-ru/translations.xml b/features/linknewdevice/impl/src/main/res/values-ru/translations.xml
index 39506417b6..6a8b645c4f 100644
--- a/features/linknewdevice/impl/src/main/res/values-ru/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-ru/translations.xml
@@ -34,6 +34,8 @@
"Если вы столкнулись с той же проблемой, попробуйте сменить точку доступа Wi-Fi или используйте мобильные данные"
"Если это не помогло, войдите вручную"
"Соединение не защищено"
+ "Вам нужно будет ввести две цифры, показанные на этом устройстве."
+ "Введите показанный номер на своем другом устройстве"
"Вход на другом устройстве был отменен."
"Запрос на вход отменен"
"Вход в систему был отклонен на другом устройстве."
@@ -54,4 +56,5 @@
"Чтобы продолжить, вам необходимо разрешить %1$s использовать камеру вашего устройства."
"Разрешите доступ к камере для сканирования QR-кода"
"Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз."
+ "Ожидание другого устройства"
diff --git a/features/linknewdevice/impl/src/main/res/values-sk/translations.xml b/features/linknewdevice/impl/src/main/res/values-sk/translations.xml
index cb430671bb..f64c01328b 100644
--- a/features/linknewdevice/impl/src/main/res/values-sk/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-sk/translations.xml
@@ -34,6 +34,8 @@
"Ak narazíte na rovnaký problém, vyskúšajte inú sieť Wi-Fi alebo namiesto siete Wi-Fi použite mobilné dáta"
"Ak to nefunguje, prihláste sa manuálne"
"Pripojenie nie je bezpečené"
+ "Budete požiadaní o zadanie dvoch číslic zobrazených na tomto zariadení."
+ "Zadajte nižšie uvedené číslo na vašom druhom zariadení"
"Prihlásenie bolo zrušené na druhom zariadení."
"Žiadosť o prihlásenie bola zrušená"
"Prihlásenie bolo zamietnuté na druhom zariadení."
@@ -54,4 +56,5 @@ Skúste sa prihlásiť manuálne alebo naskenujte QR kód pomocou iného zariade
"Ak chcete pokračovať, musíte udeliť povolenie aplikácii %1$s používať fotoaparát vášho zariadenia."
"Povoľte prístup k fotoaparátu na naskenovanie QR kódu"
"Vyskytla sa neočakávaná chyba. Prosím, skúste to znova."
+ "Čaká sa na vaše druhé zariadenie"
diff --git a/features/linknewdevice/impl/src/main/res/values-sv/translations.xml b/features/linknewdevice/impl/src/main/res/values-sv/translations.xml
index 8a1bef434a..8800b31bbd 100644
--- a/features/linknewdevice/impl/src/main/res/values-sv/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-sv/translations.xml
@@ -17,6 +17,8 @@
"Om du stöter på samma problem, prova ett annat wifi-nätverk eller använd din mobildata istället för wifi"
"Om det inte fungerar, logga in manuellt"
"Anslutningen är inte säker"
+ "Du kommer att bli ombedd att ange de två siffrorna som visas på den här enheten."
+ "Ange numret nedan på din andra enhet"
"Inloggningen avbröts på den andra enheten."
"Inloggningsförfrågan avbröts"
"Inloggningen avvisades på den andra enheten."
@@ -35,4 +37,5 @@ Prova att logga in manuellt eller skanna QR-koden med en annan enhet."
"Du måste ge tillstånd för %1$s att använda enhetens kamera för att kunna fortsätta."
"Tillåt kameraåtkomst för att skanna QR-koden"
"Ett oväntat fel inträffade. Vänligen försök igen."
+ "Väntar på din andra enhet"
diff --git a/features/linknewdevice/impl/src/main/res/values-tr/translations.xml b/features/linknewdevice/impl/src/main/res/values-tr/translations.xml
index e5fd989c0b..3c8767f64c 100644
--- a/features/linknewdevice/impl/src/main/res/values-tr/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-tr/translations.xml
@@ -33,6 +33,8 @@
"Aynı sorunla karşılaşırsanız, farklı bir wifi ağı deneyin veya wifi yerine mobil verinizi kullanın"
"Bu işe yaramazsa, manuel olarak oturum açın"
"Bağlantı güvenli değil"
+ "Bu cihazda gösterilen iki haneyi girmeniz istenecektir."
+ "Aşağıdaki numarayı diğer cihazınıza girin"
"Oturum açma işlemi diğer cihazda iptal edildi."
"Oturum açma isteği iptal edildi"
"Diğer cihazda oturum açma işlemi reddedildi."
@@ -51,4 +53,5 @@ Manuel olarak oturum açmayı deneyin veya QR kodunu başka bir cihazla tarayın
"Devam etmek için %1$s cihazınızın kamerasını kullanmasına izin vermeniz gerekir."
"QR kodunu taramak için kamera erişimine izin verin"
"Beklenmeyen bir hata oluştu. Lütfen tekrar deneyin."
+ "Diğer cihazınız bekleniyor"
diff --git a/features/linknewdevice/impl/src/main/res/values-uk/translations.xml b/features/linknewdevice/impl/src/main/res/values-uk/translations.xml
index 875b5aed16..fe5b1a7460 100644
--- a/features/linknewdevice/impl/src/main/res/values-uk/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-uk/translations.xml
@@ -33,6 +33,8 @@
"Якщо ви зіткнулися з тією ж проблемою, спробуйте іншу мережу Wi-Fi або використовуйте мобільний інтернет замість Wi-Fi"
"Якщо це не спрацює, увійдіть вручну"
"З\'єднання не безпечне"
+ "Вас попросять ввести дві цифри, показані на цьому пристрої."
+ "Введіть номер нижче на іншому пристрої"
"Вхід було скасовано на іншому пристрої."
"Запит на вхід скасовано"
"Вхід був відхилений на іншому пристрої."
@@ -53,4 +55,5 @@
"Вам потрібно дати дозвіл %1$s на використання камери вашого пристрою, щоб продовжити."
"Надайте доступ до камери, щоб сканувати QR-код"
"Сталася несподівана помилка. Будь ласка, спробуйте ще раз."
+ "Чекаємо на ваш інший пристрій"
diff --git a/features/linknewdevice/impl/src/main/res/values-ur/translations.xml b/features/linknewdevice/impl/src/main/res/values-ur/translations.xml
index 54d2c2e401..0b4bb02226 100644
--- a/features/linknewdevice/impl/src/main/res/values-ur/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-ur/translations.xml
@@ -17,6 +17,8 @@
"اگر آپ کو بھی یہی مسئلہ درپیش ہو، تو کوئی دوسرا وائی فائی شبکہ آزمائیں یا وائی فائی کے بجائے اپنے محمول بیانات استعمال کریں۔"
"اگر یہ کام نہ کرے، تو دستی طور پر داخل ہوں"
"اتصال محفوظ نہیں"
+ "آپ سے اس آلے پر دکھائے گئے دو ہندسوں کو درج کرنے کو کہا جائے گا۔"
+ "اپنے دوسرے آلے پر درج ذیل نمبر درج کریں"
"دوسرے آلے پر دخول منسوخ کر دیا گیا تھا۔"
"دخول کی درخواست منسوخ"
"دوسرے آلہ پر دخول کو مسترد کر دیا گیا تھا۔"
@@ -35,4 +37,5 @@
"جاری رکھنے کے لیے آپ %1$s کو اپنے آلے کا تصویرگر استعمال کرنے کی اجازت دینے کی ضرورت ہے۔"
"کیو آر رمز کو مسح ضوئی کرنے کے لئے تصویرگر تک رسائی کی اجازت دیں"
"ایک غیر متوقع نقص واقع ہوا۔ برائے مہربانی دوبارہ کوشش کریں۔"
+ "آپکے دوسرے آلے کا منتظر"
diff --git a/features/linknewdevice/impl/src/main/res/values-uz/translations.xml b/features/linknewdevice/impl/src/main/res/values-uz/translations.xml
index ed08ee1a04..1d436b5f4a 100644
--- a/features/linknewdevice/impl/src/main/res/values-uz/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-uz/translations.xml
@@ -34,6 +34,8 @@
"Xuddi shu muammoga duch kelsangiz, boshqa wifi tarmogʻini sinang yoki wifi oʻrniga mobil internetdan foydalaning"
"Agar bunisi ishlamasa, oddiy usulda kiring"
"Ulanish xavfsiz emas"
+ "Sizdan ushbu qurilmada koʻrsatilgan ikkita raqamni kiritish soʻraladi."
+ "Narigi qurilmada quyidagi raqamni kiriting"
"Boshqa qurilmadan hisobga kirish bekor qilindi."
"Tizimga kirish soʻrovi bekor qilindi"
"Boshqa qurilmadan hisobga kirish bekor qilindi."
@@ -54,4 +56,5 @@ Oddiy usulda kiring yoki boshqa qurilma bilan QR kodni skanerlang."
"Davom etish uchun %1$s qurilmangiz kamerasidan foydalanishiga ruxsat berishingiz kerak."
"QR kodni skanerlash uchun kameraga ruxsat bering"
"Kutilmagan xatolik yuz berdi. Qayta urining."
+ "Boshqa qurilmangiz kutilmoqda"
diff --git a/features/linknewdevice/impl/src/main/res/values-zh-rTW/translations.xml b/features/linknewdevice/impl/src/main/res/values-zh-rTW/translations.xml
index 3aa047125a..e18fbc13b6 100644
--- a/features/linknewdevice/impl/src/main/res/values-zh-rTW/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-zh-rTW/translations.xml
@@ -34,6 +34,8 @@
"如果遇到相同的問題,請嘗試使用其他 wifi 網路或您的行動數據"
"若無法運作,請手動登入"
"連線不安全"
+ "系統會要求您輸入此裝置上顯示的兩位數字。"
+ "在您的其他裝置上輸入以下數字"
"已在其他裝置上取消登入。"
"已取消登入請求"
"其他裝置拒絕登入。"
@@ -54,4 +56,5 @@
"您必須授予 %1$s 權限以使用裝置相機才能繼續。"
"允許相機權限以掃描 QR code"
"發生意外錯誤。請再試一次。"
+ "等待您的其他裝置"
diff --git a/features/linknewdevice/impl/src/main/res/values-zh/translations.xml b/features/linknewdevice/impl/src/main/res/values-zh/translations.xml
index 10ae1ae7a7..cabcb5ccd0 100644
--- a/features/linknewdevice/impl/src/main/res/values-zh/translations.xml
+++ b/features/linknewdevice/impl/src/main/res/values-zh/translations.xml
@@ -1,18 +1,18 @@
"扫描二维码"
- "在笔记本电脑或台式机上打开%1$s "
+ "在笔记本电脑或台式机上打开 %1$s"
"使用此设备扫描二维码"
"准备进行扫描"
- "在电脑上打开%1$s 获取二维码"
+ "在台式电脑上打开 %1$s 以获取二维码"
"数字不匹配"
- "输入两位数的验证码"
- "这将验证您与其他设备的连接是否安全。"
+ "输入两位数字的代码"
+ "这将验证你与其它设备的连接是否安全。"
"请输入另一台设备上显示的数字"
"账户提供方不支持 %1$s."
"不支持 %1$s."
- "您的账户提供商不支持使用二维码登录新设备。"
- "不支持二维码"
+ "你的账户提供者不支持使用二维码登录到新设备。"
+ "二维码不受支持"
"登录被另一台设备取消"
"登录请求已取消"
"登录已过期. 请重试."
@@ -25,33 +25,36 @@
"台式计算机"
"正在加载二维码…"
"移动设备"
- "您想连接哪种类型的设备?"
- "请重试,并确保您已正确输入两位验证码。如果验证码仍然不匹配,请联系您的账户提供商。"
+ "你想连接哪种类型的设备?"
+ "请重试,并确保已正确输入两位数字的代码。如果数字仍然不匹配,请联系账户提供者。"
"数字不匹配"
- "无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。"
+ "无法与新设备建立安全连接。你的现有设备仍然安全,无需担心。"
"现在怎么办?"
"如果这是网络问题,请尝试使用二维码再次登录"
"如果遇到同样的问题,请尝试使用不同的 WiFi 网络或使用移动数据代替 WiFi"
"如果不起作用,请手动登录"
"连接不安全"
+ "你将被要求输入此设备上显示的两位数字。"
+ "在你的其它设备上输入以下数字"
"登录被另一台设备取消"
"登录请求已取消"
- "其它设备未接受请求"
+ "另一设备上的登录请求已被拒绝。"
"登录被拒绝"
- "您无需额外操作。"
- "您已在另一台设备登录。"
+ "无需额外操作。"
+ "你已在另一设备上登录。"
"登录已过期. 请重试."
"登录未及时完成"
"另一个设备不支持使用二维码登录 %s.
尝试手动或使用另一个设备扫描二维码."
- "不支持二维码"
+ "二维码不受支持"
"账户提供方不支持 %1$s."
"不支持 %1$s."
- "使用其他设备上显示的二维码。"
- "再试一次"
+ "使用其它设备上显示的二维码。"
+ "重试"
"二维码错误"
- "您需要授予 %1$s 使用设备摄像头的权限才能继续。"
- "允许摄像头权限以扫描 QR 码"
+ "你需要授予 %1$s 使用设备摄像头的权限才能继续。"
+ "允许访问摄像头以扫描二维码"
"发生了意外错误。请再试一次。"
+ "正在等待其它设备"
diff --git a/features/linknewdevice/impl/src/main/res/values/localazy.xml b/features/linknewdevice/impl/src/main/res/values/localazy.xml
index 321b168751..6ffcce227a 100644
--- a/features/linknewdevice/impl/src/main/res/values/localazy.xml
+++ b/features/linknewdevice/impl/src/main/res/values/localazy.xml
@@ -34,6 +34,8 @@
"If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi"
"If that doesn’t work, sign in manually"
"Connection not secure"
+ "You’ll be asked to enter the two digits shown on this device."
+ "Enter the number below on your other device"
"The sign in was cancelled on the other device."
"Sign in request cancelled"
"The sign in was declined on the other device."
@@ -54,4 +56,5 @@ Try signing in manually, or scan the QR code with another device."
"You need to give permission for %1$s to use your device’s camera in order to continue."
"Allow camera access to scan the QR code"
"An unexpected error occurred. Please try again."
+ "Waiting for your other device"
diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt
index 2957a89495..1b2af8f4c3 100644
--- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt
+++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt
@@ -11,6 +11,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
import com.google.common.truth.Truth.assertThat
+import io.element.android.features.enterprise.test.FakeSessionEnterpriseService
import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.tests.testutils.lambda.lambdaError
@@ -37,6 +38,7 @@ class DefaultLinkNewDeviceEntryPointTest {
sessionCoroutineScope = backgroundScope,
linkNewMobileHandler = LinkNewMobileHandler(client),
linkNewDesktopHandler = LinkNewDesktopHandler(client),
+ sessionEnterpriseService = FakeSessionEnterpriseService(),
)
}
val callback: LinkNewDeviceEntryPoint.Callback = object : LinkNewDeviceEntryPoint.Callback {
diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt
index ac0a129f49..7609acf809 100644
--- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt
+++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt
@@ -5,11 +5,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.linknewdevice.impl.screens.desktop
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.linknewdevice.impl.R
import io.element.android.tests.testutils.EnsureNeverCalled
@@ -18,42 +21,37 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DesktopNoticeViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the expected callback`() {
+ fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setView(
+ setView(
state = aDesktopNoticeState(),
onBackClicked = callback,
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `on back button clicked - calls the expected callback`() {
+ fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setView(
+ setView(
state = aDesktopNoticeState(),
onBackClicked = callback,
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `when can continue - calls the expected callback`() {
+ fun `when can continue - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setView(
+ setView(
state = aDesktopNoticeState(canContinue = true),
onReadyToScanClick = callback,
)
@@ -61,16 +59,16 @@ class DesktopNoticeViewTest {
}
@Test
- fun `on submit button clicked - emits the Continue event`() {
+ fun `on submit button clicked - emits the Continue event`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder()
- rule.setView(
+ setView(
state = aDesktopNoticeState(eventSink = eventRecorder),
)
- rule.clickOn(R.string.screen_link_new_device_desktop_submit)
+ clickOn(R.string.screen_link_new_device_desktop_submit)
eventRecorder.assertSingle(DesktopNoticeEvent.Continue)
}
- private fun AndroidComposeTestRule.setView(
+ private fun AndroidComposeUiTest.setView(
state: DesktopNoticeState,
onBackClicked: () -> Unit = EnsureNeverCalled(),
onReadyToScanClick: () -> Unit = EnsureNeverCalled(),
diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt
index aa52a70149..b63d7471ac 100644
--- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt
+++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt
@@ -5,58 +5,56 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.linknewdevice.impl.screens.error
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ErrorViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the onCancel callback`() {
+ fun `on back pressed - calls the onCancel callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setErrorView(
+ setErrorView(
onCancel = callback,
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `on try again button clicked - calls the expected callback`() {
+ fun `on try again button clicked - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setErrorView(
+ setErrorView(
onRetry = callback
)
- rule.clickOn(CommonStrings.action_try_again)
+ clickOn(CommonStrings.action_try_again)
}
}
@Test
- fun `on cancel button clicked - calls the expected callback`() {
+ fun `on cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setErrorView(
+ setErrorView(
onCancel = callback
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
}
}
- private fun AndroidComposeTestRule.setErrorView(
+ private fun AndroidComposeUiTest.setErrorView(
onRetry: () -> Unit = EnsureNeverCalled(),
onCancel: () -> Unit = EnsureNeverCalled(),
errorScreenType: ErrorScreenType = ErrorScreenType.UnknownError,
diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt
index 20e1d898dd..25dc9efa8a 100644
--- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt
+++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt
@@ -5,13 +5,16 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.linknewdevice.impl.screens.number
import androidx.activity.ComponentActivity
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertIsNotEnabled
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
@@ -20,65 +23,60 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class EnterNumberViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the expected callback`() {
+ fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setView(
+ setView(
state = aEnterNumberState(),
onBackClicked = callback,
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `on back button clicked - calls the expected callback`() {
+ fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setView(
+ setView(
state = aEnterNumberState(),
onBackClicked = callback,
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `on continue button clicked - emits the Continue event`() {
+ fun `on continue button clicked - emits the Continue event`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder()
- rule.setView(
+ setView(
state = aEnterNumberState(
number = "12",
eventSink = eventRecorder,
),
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventRecorder.assertSingle(EnterNumberEvent.Continue)
}
@Test
- fun `when the number is not complete, continue button is disabled`() {
+ fun `when the number is not complete, continue button is disabled`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder(expectEvents = false)
- rule.setView(
+ setView(
state = aEnterNumberState(
number = "1",
eventSink = eventRecorder,
),
)
- val continueStr = rule.activity.getString(CommonStrings.action_continue)
- rule.onNodeWithText(continueStr).assertIsNotEnabled()
+ val continueStr = activity!!.getString(CommonStrings.action_continue)
+ onNodeWithText(continueStr).assertIsNotEnabled()
}
- private fun AndroidComposeTestRule.setView(
+ private fun AndroidComposeUiTest.setView(
state: EnterNumberState,
onBackClicked: () -> Unit = EnsureNeverCalled(),
) {
diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt
index c6c89ba818..d552c2bff6 100644
--- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt
+++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt
@@ -5,36 +5,34 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.linknewdevice.impl.screens.qrcode
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ShowQrCodeViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the expected callback`() {
+ fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setView(
+ setView(
onBackClick = callback
)
- rule.pressBackKey()
+ pressBackKey()
}
}
- private fun AndroidComposeTestRule.setView(
+ private fun AndroidComposeUiTest.setView(
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
setContent {
diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt
index e352debfb0..bceb8753b2 100644
--- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt
+++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt
@@ -5,11 +5,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.linknewdevice.impl.screens.root
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.linknewdevice.impl.R
import io.element.android.libraries.architecture.AsyncData
@@ -19,74 +22,69 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LinkNewDeviceRootViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the onRetry callback`() {
+ fun `on back pressed - calls the onRetry callback`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setLinkNewDeviceRootView(
+ setLinkNewDeviceRootView(
state = aLinkNewDeviceRootState(
eventSink = eventRecorder,
),
onBackClick = callback
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `link desktop button clicked - calls the expected callback`() {
+ fun `link desktop button clicked - calls the expected callback`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setLinkNewDeviceRootView(
+ setLinkNewDeviceRootView(
state = aLinkNewDeviceRootState(
isSupported = AsyncData.Success(true),
eventSink = eventRecorder,
),
onLinkDesktopDeviceClick = callback,
)
- rule.clickOn(R.string.screen_link_new_device_root_desktop_computer)
+ clickOn(R.string.screen_link_new_device_root_desktop_computer)
}
}
@Test
- fun `link mobile button clicked - emits the expected event`() {
+ fun `link mobile button clicked - emits the expected event`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder()
- rule.setLinkNewDeviceRootView(
+ setLinkNewDeviceRootView(
state = aLinkNewDeviceRootState(
isSupported = AsyncData.Success(true),
eventSink = eventRecorder,
)
)
- rule.clickOn(R.string.screen_link_new_device_root_mobile_device)
+ clickOn(R.string.screen_link_new_device_root_mobile_device)
eventRecorder.assertSingle(LinkNewDeviceRootEvent.LinkMobileDevice)
}
@Test
- fun `not supported - dismiss click - invokes the expected callback`() {
+ fun `not supported - dismiss click - invokes the expected callback`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setLinkNewDeviceRootView(
+ setLinkNewDeviceRootView(
state = aLinkNewDeviceRootState(
isSupported = AsyncData.Success(false),
eventSink = eventRecorder,
),
onBackClick = callback,
)
- rule.clickOn(CommonStrings.action_dismiss)
+ clickOn(CommonStrings.action_dismiss)
}
}
- private fun AndroidComposeTestRule.setLinkNewDeviceRootView(
+ private fun AndroidComposeUiTest.setLinkNewDeviceRootView(
state: LinkNewDeviceRootState = aLinkNewDeviceRootState(),
onBackClick: () -> Unit = EnsureNeverCalled(),
onLinkDesktopDeviceClick: () -> Unit = EnsureNeverCalled(),
diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt
index fcc3afeb7d..1932718fef 100644
--- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt
+++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt
@@ -5,11 +5,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.linknewdevice.impl.screens.scan
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.test.AN_EXCEPTION
@@ -19,44 +22,39 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ScanQrCodeViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the expected callback`() {
+ fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setView(
+ setView(
state = aScanQrCodeState(
eventSink = eventRecorder,
),
onBackClick = callback
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `try again button clicked - emits the expected event`() {
+ fun `try again button clicked - emits the expected event`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder()
- rule.setView(
+ setView(
state = aScanQrCodeState(
scanAction = AsyncAction.Failure(AN_EXCEPTION),
eventSink = eventRecorder,
)
)
- rule.clickOn(CommonStrings.action_try_again)
+ clickOn(CommonStrings.action_try_again)
eventRecorder.assertSingle(ScanQrCodeEvent.TryAgain)
}
- private fun AndroidComposeTestRule.setView(
+ private fun AndroidComposeUiTest.setView(
state: ScanQrCodeState = aScanQrCodeState(),
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/LocationShareRow.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/LocationShareRow.kt
index 83db9a9c61..3d7b8df618 100644
--- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/LocationShareRow.kt
+++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/LocationShareRow.kt
@@ -31,6 +31,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
@@ -116,10 +118,10 @@ internal fun LocationShareRowPreview() = ElementPreview {
LocationShareRow(
item = LocationShareItem(
userId = UserId("@alice:matrix.org"),
- displayName = "Alice",
+ displayName = USER_NAME_ALICE,
avatarData = AvatarData(
id = "@alice:matrix.org",
- name = "Alice",
+ name = USER_NAME_ALICE,
url = null,
size = AvatarSize.UserListItem,
),
@@ -133,10 +135,10 @@ internal fun LocationShareRowPreview() = ElementPreview {
LocationShareRow(
item = LocationShareItem(
userId = UserId("@bob:matrix.org"),
- displayName = "Bob",
+ displayName = USER_NAME_BOB,
avatarData = AvatarData(
id = "@bob:matrix.org",
- name = "Bob",
+ name = USER_NAME_BOB,
url = null,
size = AvatarSize.UserListItem,
),
diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt
index 1ab2310365..2bbf4ae34e 100644
--- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt
+++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt
@@ -13,6 +13,7 @@ import io.element.android.features.location.api.Location
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.AssetType
import kotlinx.collections.immutable.toImmutableList
@@ -69,7 +70,7 @@ fun aShowLocationState(
fun aLocationShareItem(
userId: UserId = UserId("@alice:matrix.org"),
- displayName: String = "Alice",
+ displayName: String = USER_NAME_ALICE,
avatarData: AvatarData = AvatarData(
id = userId.value,
name = displayName,
diff --git a/features/location/impl/src/main/res/values-cs/translations.xml b/features/location/impl/src/main/res/values-cs/translations.xml
index 99deeba029..6bb5b1db8c 100644
--- a/features/location/impl/src/main/res/values-cs/translations.xml
+++ b/features/location/impl/src/main/res/values-cs/translations.xml
@@ -1,4 +1,5 @@
+ "Vaše historie aktuální polohy bude uložena v místnosti a bude viditelná pro členy i po skončení relace."
"Zvolte, jak dlouho chcete sdílet svou aktuální polohu."
diff --git a/features/location/impl/src/main/res/values-da/translations.xml b/features/location/impl/src/main/res/values-da/translations.xml
index f15ab0fb2f..4d4c5d00bc 100644
--- a/features/location/impl/src/main/res/values-da/translations.xml
+++ b/features/location/impl/src/main/res/values-da/translations.xml
@@ -1,4 +1,5 @@
+ "Din live-positionshistorik gemmes i rummet og er synlig for medlemmerne, når sessionen er afsluttet."
"Vælg, hvor længe du vil dele din aktuelle position."
diff --git a/features/location/impl/src/main/res/values-zh/translations.xml b/features/location/impl/src/main/res/values-zh/translations.xml
new file mode 100644
index 0000000000..6837d3a1eb
--- /dev/null
+++ b/features/location/impl/src/main/res/values-zh/translations.xml
@@ -0,0 +1,5 @@
+
+
+ "你实时位置历史将存储在房间中,并于会话结束后对其他成员可见。"
+ "选择共享实时位置的时长。"
+
diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationViewTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationViewTest.kt
index 317fbf8fed..63c19ba913 100644
--- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationViewTest.kt
+++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationViewTest.kt
@@ -5,15 +5,18 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.location.impl.share
import androidx.activity.ComponentActivity
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.libraries.testtags.TestTags
@@ -23,102 +26,98 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ShareLocationViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `test back action`() {
+ fun `test back action`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setShareLocationView(
+ setShareLocationView(
state = aShareLocationState(
eventSink = eventsRecorder
),
navigateUp = callback,
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `test fab click`() {
+ fun `test fab click`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShareLocationView(
+ setShareLocationView(
aShareLocationState(
eventSink = eventsRecorder
),
navigateUp = EnsureNeverCalled(),
)
- rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick()
+ onNodeWithTag(TestTags.floatingActionButton.value).performClick()
eventsRecorder.assertSingle(ShareLocationEvent.StartTrackingUserLocation)
}
@Test
- fun `when permission denied is displayed user can open the settings`() {
+ fun `when permission denied is displayed user can open the settings`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShareLocationView(
+ setShareLocationView(
aShareLocationState(
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
eventSink = eventsRecorder
),
navigateUp = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(ShareLocationEvent.OpenAppSettings)
}
@Test
- fun `when permission denied is displayed user can close the dialog`() {
+ fun `when permission denied is displayed user can close the dialog`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShareLocationView(
+ setShareLocationView(
aShareLocationState(
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
eventSink = eventsRecorder
),
navigateUp = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
}
@Test
- fun `when permission rationale is displayed user can request permissions`() {
+ fun `when permission rationale is displayed user can request permissions`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShareLocationView(
+ setShareLocationView(
aShareLocationState(
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
eventSink = eventsRecorder
),
navigateUp = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(ShareLocationEvent.RequestPermissions)
}
@Test
- fun `when permission rationale is displayed user can close the dialog`() {
+ fun `when permission rationale is displayed user can close the dialog`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShareLocationView(
+ setShareLocationView(
aShareLocationState(
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
eventSink = eventsRecorder
),
navigateUp = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
}
@Test
- fun `when location service disabled is displayed user can open location settings`() {
+ fun `when location service disabled is displayed user can open location settings`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShareLocationView(
+ setShareLocationView(
aShareLocationState(
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
hasLocationPermission = true,
@@ -126,14 +125,14 @@ class ShareLocationViewTest {
),
navigateUp = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(ShareLocationEvent.OpenLocationSettings)
}
@Test
- fun `when location service disabled is displayed user can close the dialog`() {
+ fun `when location service disabled is displayed user can close the dialog`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShareLocationView(
+ setShareLocationView(
aShareLocationState(
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
hasLocationPermission = true,
@@ -141,12 +140,12 @@ class ShareLocationViewTest {
),
navigateUp = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
}
}
-private fun AndroidComposeTestRule.setShareLocationView(
+private fun AndroidComposeUiTest.setShareLocationView(
state: ShareLocationState,
navigateUp: () -> Unit = EnsureNeverCalled(),
) {
diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt
index fecbbdbf89..45ed894f97 100644
--- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt
+++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt
@@ -6,16 +6,19 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.location.impl.show
import androidx.activity.ComponentActivity
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.location.api.Location
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
@@ -26,115 +29,111 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ShowLocationViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `test back action`() {
+ fun `test back action`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setShowLocationView(
+ setShowLocationView(
state = aShowLocationState(
eventSink = eventsRecorder
),
onBackClick = callback,
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `test share action`() {
+ fun `test share action`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShowLocationView(
+ setShowLocationView(
aShowLocationState(
eventSink = eventsRecorder
),
onBackClick = EnsureNeverCalled(),
)
- val shareContentDescription = rule.activity.getString(CommonStrings.action_share)
- rule.onNodeWithContentDescription(shareContentDescription).performClick()
+ val shareContentDescription = activity!!.getString(CommonStrings.action_share)
+ onNodeWithContentDescription(shareContentDescription).performClick()
// The default aStaticLocationMode uses Location(1.23, 2.34, 4f)
eventsRecorder.assertSingle(ShowLocationEvent.Share(Location(1.23, 2.34, 4f)))
}
@Test
- fun `test fab click`() {
+ fun `test fab click`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShowLocationView(
+ setShowLocationView(
aShowLocationState(
eventSink = eventsRecorder
),
onBackClick = EnsureNeverCalled(),
)
- rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick()
+ onNodeWithTag(TestTags.floatingActionButton.value).performClick()
eventsRecorder.assertSingle(ShowLocationEvent.TrackMyLocation(true))
}
@Test
- fun `when permission denied is displayed user can open the settings`() {
+ fun `when permission denied is displayed user can open the settings`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShowLocationView(
+ setShowLocationView(
aShowLocationState(
constraintsDialogState = LocationConstraintsDialogState.PermissionDenied,
eventSink = eventsRecorder
),
onBackClick = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(ShowLocationEvent.OpenAppSettings)
}
@Test
- fun `when permission denied is displayed user can close the dialog`() {
+ fun `when permission denied is displayed user can close the dialog`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShowLocationView(
+ setShowLocationView(
aShowLocationState(
constraintsDialogState = LocationConstraintsDialogState.PermissionDenied,
eventSink = eventsRecorder
),
onBackClick = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(ShowLocationEvent.DismissDialog)
}
@Test
- fun `when permission rationale is displayed user can request permissions`() {
+ fun `when permission rationale is displayed user can request permissions`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShowLocationView(
+ setShowLocationView(
aShowLocationState(
constraintsDialogState = LocationConstraintsDialogState.PermissionRationale,
eventSink = eventsRecorder
),
onBackClick = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(ShowLocationEvent.RequestPermissions)
}
@Test
- fun `when permission rationale is displayed user can close the dialog`() {
+ fun `when permission rationale is displayed user can close the dialog`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setShowLocationView(
+ setShowLocationView(
aShowLocationState(
constraintsDialogState = LocationConstraintsDialogState.PermissionRationale,
eventSink = eventsRecorder
),
onBackClick = EnsureNeverCalled(),
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(ShowLocationEvent.DismissDialog)
}
}
-private fun AndroidComposeTestRule.setShowLocationView(
+private fun AndroidComposeUiTest.setShowLocationView(
state: ShowLocationState,
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
diff --git a/features/location/test/build.gradle.kts b/features/location/test/build.gradle.kts
index f84e8ba772..e51737d40c 100644
--- a/features/location/test/build.gradle.kts
+++ b/features/location/test/build.gradle.kts
@@ -16,7 +16,7 @@ android {
dependencies {
api(projects.features.location.api)
+ implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
- implementation(libs.appyx.core)
implementation(projects.tests.testutils)
}
diff --git a/features/lockscreen/impl/src/main/res/values-fa/translations.xml b/features/lockscreen/impl/src/main/res/values-fa/translations.xml
index 56dc91e835..bd2000b94d 100644
--- a/features/lockscreen/impl/src/main/res/values-fa/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-fa/translations.xml
@@ -34,5 +34,5 @@
"استفاده از زیستسنجی"
"استفاده از پین"
- "خارج شدن…"
+ "برداشتن افزاره…"
diff --git a/features/lockscreen/impl/src/main/res/values-in/translations.xml b/features/lockscreen/impl/src/main/res/values-in/translations.xml
index 0396f56b0c..e0054cda62 100644
--- a/features/lockscreen/impl/src/main/res/values-in/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-in/translations.xml
@@ -32,5 +32,5 @@ Pilih sesuatu yang mudah untuk diingat. Jika Anda lupa PIN ini, Anda akan dikelu
"Gunakan biometrik"
"Gunakan PIN"
- "Mengeluarkan dari akun…"
+ "Mengeluarkan device dari akun…"
diff --git a/features/lockscreen/impl/src/main/res/values-pt/translations.xml b/features/lockscreen/impl/src/main/res/values-pt/translations.xml
index a6b2516fba..fca6fbcb9e 100644
--- a/features/lockscreen/impl/src/main/res/values-pt/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-pt/translations.xml
@@ -23,7 +23,7 @@ Escolhe algo memorável. Se te esqueceres deste PIN, a tua sessão será termina
"Insere o mesmo PIN duas vezes"
"Os PINs não coincidem"
"Terás de voltar a iniciar sessão e criar um novo PIN para continuar"
- "Estás a terminar a sessão"
+ "O teu dispositivo está a ser removido"
- "Tens %1$d tentativa de desbloqueio"
- "Tens %1$d tentativas de desbloqueio"
@@ -34,5 +34,5 @@ Escolhe algo memorável. Se te esqueceres deste PIN, a tua sessão será termina
"Utilizar biometria"
"Utilizar PIN"
- "A terminar sessão…"
+ "A remover dispositivo…"
diff --git a/features/lockscreen/impl/src/main/res/values-ro/translations.xml b/features/lockscreen/impl/src/main/res/values-ro/translations.xml
index d40bfbaece..7555d7eb58 100644
--- a/features/lockscreen/impl/src/main/res/values-ro/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-ro/translations.xml
@@ -23,7 +23,7 @@ Alegeți ceva memorabil. Dacă uitați acest PIN, veți fi deconectat din aplica
"Vă rugăm să introduceți același cod PIN de două ori"
"Codurile PIN nu corespund"
"Va trebui să vă reconectați și să creați un cod PIN nou pentru a continua"
- "Sunteți deconectat"
+ "Acest device este în curs de eliminare"
- "Aveți %1$d încercare de deblocare"
- "Aveți %1$d încercări de deblocare"
diff --git a/features/lockscreen/impl/src/main/res/values-zh/translations.xml b/features/lockscreen/impl/src/main/res/values-zh/translations.xml
index b83c7d4e85..f3e93668fd 100644
--- a/features/lockscreen/impl/src/main/res/values-zh/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-zh/translations.xml
@@ -8,21 +8,21 @@
"更改 PIN 码"
"允许生物识别解锁"
"移除 PIN 码"
- "您确定要删除 PIN 码吗?"
+ "你确定要删除 PIN 码?"
"移除 PIN 码?"
"允许 %1$s"
"我宁愿使用 PIN 码"
"节省时间,用 %1$s 来解锁应用程序"
"选择 PIN 码"
"确认 PIN 码"
- "锁定 %1$s 以为聊天增加安全性。
+ "锁定 %1$s 以增加聊天的安全性。
-选择好记的 PIN 码。如果忘掉了这个 PIN 码,就不得不登出应用。"
- "出于安全原因,您不能选择这个 PIN 码"
+选择好记的 PIN 码。如果忘掉了此 PIN 码,你将被迫从 app 注销。"
+ "出于安全考虑,你不能使用此 PIN 码"
"选择不同的 PIN 码"
"请输入两次相同的 PIN 码"
"PIN 码不匹配"
- "您需要重新登录并创建新的 PIN 才能继续"
+ "你需要重新登录并创建新的 PIN 码才能继续"
"正在被移除该设备"
- "还剩 %1$d 次解锁机会"
@@ -32,5 +32,5 @@
"使用生物识别"
"使用 PIN 码"
- "正在删除设备……"
+ "正在移除设备…"
diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt
index 1ecb79bd67..e6d1659778 100644
--- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt
+++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt
@@ -6,60 +6,57 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.lockscreen.impl.unlock.keypad
import android.view.KeyEvent
import androidx.activity.ComponentActivity
import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isRoot
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.pressKey
import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.compose.ui.unit.dp
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class PinKeypadTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on a number emits the expected event`() {
+ fun `clicking on a number emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setPinKeyPad(onClick = eventsRecorder)
- rule.onNode(hasText("1")).performClick()
+ setPinKeyPad(onClick = eventsRecorder)
+ onNode(hasText("1")).performClick()
eventsRecorder.assertSingle(PinKeypadModel.Number('1'))
}
@Test
- fun `clicking on the delete previous character button emits the expected event`() {
+ fun `clicking on the delete previous character button emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setPinKeyPad(onClick = eventsRecorder)
- rule.onNode(hasContentDescription(rule.activity.getString(CommonStrings.a11y_delete))).performClick()
+ setPinKeyPad(onClick = eventsRecorder)
+ onNode(hasContentDescription(activity!!.getString(CommonStrings.a11y_delete))).performClick()
eventsRecorder.assertSingle(PinKeypadModel.Back)
}
@OptIn(ExperimentalTestApi::class)
@Test
- fun `typing using the hardware keyboard emits the expected events`() {
+ fun `typing using the hardware keyboard emits the expected events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setPinKeyPad(onClick = eventsRecorder)
- rule.onNodeWithText("1").requestFocus()
- rule.onAllNodes(isRoot())[0].performKeyInput {
+ setPinKeyPad(onClick = eventsRecorder)
+ onNodeWithText("1").requestFocus()
+ onAllNodes(isRoot())[0].performKeyInput {
val keys = listOf(
Key.A,
Key.NumPad1,
@@ -118,7 +115,7 @@ class PinKeypadTest {
)
}
- private fun AndroidComposeTestRule.setPinKeyPad(
+ private fun AndroidComposeUiTest.setPinKeyPad(
onClick: (PinKeypadModel) -> Unit = EnsureNeverCalledWithParam(),
) {
setContent {
diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts
index 12af922cbe..e739beb20a 100644
--- a/features/login/impl/build.gradle.kts
+++ b/features/login/impl/build.gradle.kts
@@ -69,7 +69,7 @@ dependencies {
implementation(projects.libraries.permissions.api)
implementation(projects.libraries.sessionStorage.api)
implementation(projects.libraries.qrcode)
- implementation(projects.libraries.oidc.api)
+ implementation(projects.libraries.oauth.api)
implementation(projects.libraries.uiUtils)
implementation(projects.libraries.wellknown.api)
implementation(libs.androidx.browser)
@@ -83,7 +83,7 @@ dependencies {
testImplementation(projects.features.preferences.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.matrix.test)
- testImplementation(projects.libraries.oidc.test)
+ testImplementation(projects.libraries.oauth.test)
testImplementation(projects.libraries.permissions.test)
testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.libraries.wellknown.test)
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt
index fb384d505a..978d28dfa3 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt
@@ -50,9 +50,9 @@ import io.element.android.libraries.architecture.callback
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.annotations.AppCoroutineScope
-import io.element.android.libraries.matrix.api.auth.OidcDetails
-import io.element.android.libraries.oidc.api.OidcAction
-import io.element.android.libraries.oidc.api.OidcActionFlow
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
+import io.element.android.libraries.oauth.api.OAuthAction
+import io.element.android.libraries.oauth.api.OAuthActionFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -64,7 +64,7 @@ class LoginFlowNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List,
private val accountProviderDataSource: AccountProviderDataSource,
- private val oidcActionFlow: OidcActionFlow,
+ private val oAuthActionFlow: OAuthActionFlow,
@AppCoroutineScope
private val appCoroutineScope: CoroutineScope,
private val elementClassicConnection: ElementClassicConnection,
@@ -100,7 +100,7 @@ class LoginFlowNode(
// by pressing back or by closing the Custom Chrome Tab.
lifecycleScope.launch {
delay(5000)
- oidcActionFlow.post(OidcAction.GoBack(toUnblock = true))
+ oAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true))
}
}
}
@@ -161,8 +161,8 @@ class LoginFlowNode(
backstack.push(NavTarget.LoginPassword())
}
- override fun navigateToOidc(oidcDetails: OidcDetails) {
- navigateToMas(oidcDetails)
+ override fun navigateToOAuth(oAuthDetails: OAuthDetails) {
+ navigateToMas(oAuthDetails)
}
override fun navigateToCreateAccount(url: String) {
@@ -197,8 +197,8 @@ class LoginFlowNode(
callback.navigateToBugReport()
}
- override fun navigateToOidc(oidcDetails: OidcDetails) {
- navigateToMas(oidcDetails)
+ override fun navigateToOAuth(oAuthDetails: OAuthDetails) {
+ navigateToMas(oAuthDetails)
}
override fun navigateToCreateAccount(url: String) {
@@ -243,8 +243,8 @@ class LoginFlowNode(
}
NavTarget.ChooseAccountProvider -> {
val callback = object : ChooseAccountProviderNode.Callback {
- override fun navigateToOidc(oidcDetails: OidcDetails) {
- navigateToMas(oidcDetails)
+ override fun navigateToOAuth(oAuthDetails: OAuthDetails) {
+ navigateToMas(oAuthDetails)
}
override fun navigateToCreateAccount(url: String) {
@@ -270,8 +270,8 @@ class LoginFlowNode(
isAccountCreation = navTarget.isAccountCreation,
)
val callback = object : ConfirmAccountProviderNode.Callback {
- override fun navigateToOidc(oidcDetails: OidcDetails) {
- navigateToMas(oidcDetails)
+ override fun navigateToOAuth(oAuthDetails: OAuthDetails) {
+ navigateToMas(oAuthDetails)
}
override fun navigateToCreateAccount(url: String) {
@@ -333,10 +333,10 @@ class LoginFlowNode(
}
}
- private fun navigateToMas(oidcDetails: OidcDetails) {
+ private fun navigateToMas(oAuthDetails: OAuthDetails) {
activity?.let {
externalAppStarted = true
- it.openUrlInChromeCustomTab(null, darkTheme, oidcDetails.url)
+ it.openUrlInChromeCustomTab(null, darkTheme, oAuthDetails.url)
}
}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt
index 2f4af14237..560e6123c1 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt
@@ -41,7 +41,7 @@ sealed class ChangeServerError : Exception() {
// AccountAlreadyLoggedIn error should not happen at this point
is AuthenticationException.AccountAlreadyLoggedIn -> Error(messageStr = error.message)
is AuthenticationException.Generic -> Error(messageStr = error.message)
- is AuthenticationException.Oidc -> Error(messageStr = error.message)
+ is AuthenticationException.OAuth -> Error(messageStr = error.message)
}
}
is AccountProviderAccessException.NeedElementProException -> NeedElementPro(
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt
index 78be770bfc..3c871a8a1d 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt
@@ -23,9 +23,9 @@ import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationR
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
-import io.element.android.libraries.matrix.api.auth.OidcPrompt
-import io.element.android.libraries.oidc.api.OidcAction
-import io.element.android.libraries.oidc.api.OidcActionFlow
+import io.element.android.libraries.matrix.api.auth.OAuthPrompt
+import io.element.android.libraries.oauth.api.OAuthAction
+import io.element.android.libraries.oauth.api.OAuthActionFlow
/**
* This class is responsible for managing the login flow, including handling OIDC actions and
@@ -35,7 +35,7 @@ import io.element.android.libraries.oidc.api.OidcActionFlow
*/
@Inject
class LoginHelper(
- private val oidcActionFlow: OidcActionFlow,
+ private val oAuthActionFlow: OAuthActionFlow,
private val authenticationService: MatrixAuthenticationService,
private val webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever,
) {
@@ -44,9 +44,9 @@ class LoginHelper(
@Composable
fun collectLoginMode(): State> {
LaunchedEffect(Unit) {
- oidcActionFlow.collect { oidcAction ->
- if (oidcAction != null) {
- onOidcAction(oidcAction)
+ oAuthActionFlow.collect { oAuthAction ->
+ if (oAuthAction != null) {
+ onOAuthAction(oAuthAction)
}
}
}
@@ -73,11 +73,11 @@ class LoginHelper(
throw it
}
}.map { matrixHomeServerDetails ->
- if (matrixHomeServerDetails.supportsOidcLogin) {
+ if (matrixHomeServerDetails.supportsOAuthLogin) {
// Retrieve the details right now
- val oidcPrompt = if (isAccountCreation) OidcPrompt.Create else OidcPrompt.Login
- LoginMode.Oidc(
- authenticationService.getOidcUrl(prompt = oidcPrompt, loginHint = loginHint).getOrThrow()
+ val oAuthPrompt = if (isAccountCreation) OAuthPrompt.Create else OAuthPrompt.Login
+ LoginMode.OAuth(
+ authenticationService.getOAuthUrl(prompt = oAuthPrompt, loginHint = loginHint).getOrThrow()
)
} else if (isAccountCreation) {
val url = webClientUrlForAuthenticationRetriever.retrieve(homeserverUrl)
@@ -99,16 +99,16 @@ class LoginHelper(
)
}
- private suspend fun onOidcAction(oidcAction: OidcAction) {
- if (oidcAction is OidcAction.GoBack && oidcAction.toUnblock && loginModeState.value !is AsyncData.Loading) {
+ private suspend fun onOAuthAction(oAuthAction: OAuthAction) {
+ if (oAuthAction is OAuthAction.GoBack && oAuthAction.toUnblock && loginModeState.value !is AsyncData.Loading) {
// Ignore GoBack action if the current state is not Loading. This GoBack action is coming from LoginFlowNode.
// This can happen if there is an error, for instance attempt to login again on the same account.
return
}
loginModeState.value = AsyncData.Loading()
- when (oidcAction) {
- is OidcAction.GoBack -> {
- authenticationService.cancelOidcLogin()
+ when (oAuthAction) {
+ is OAuthAction.GoBack -> {
+ authenticationService.cancelOAuthLogin()
.onSuccess {
loginModeState.value = AsyncData.Uninitialized
}
@@ -116,13 +116,13 @@ class LoginHelper(
loginModeState.value = AsyncData.Failure(failure)
}
}
- is OidcAction.Success -> {
- authenticationService.loginWithOidc(oidcAction.url)
+ is OAuthAction.Success -> {
+ authenticationService.loginWithOAuth(oAuthAction.url)
.onFailure { failure ->
loginModeState.value = AsyncData.Failure(failure)
}
}
}
- oidcActionFlow.reset()
+ oAuthActionFlow.reset()
}
}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt
index 08e604ef20..5ea52e0ebd 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt
@@ -8,10 +8,10 @@
package io.element.android.features.login.impl.login
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
sealed interface LoginMode {
data object PasswordLogin : LoginMode
- data class Oidc(val oidcDetails: OidcDetails) : LoginMode
+ data class OAuth(val oAuthDetails: OAuthDetails) : LoginMode
data class AccountCreation(val url: String) : LoginMode
}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt
index f88e34bf4a..3549e17457 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt
@@ -24,7 +24,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
import io.element.android.libraries.matrix.api.auth.AuthenticationException
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
@@ -32,7 +32,7 @@ fun LoginModeView(
loginMode: AsyncData,
onClearError: () -> Unit,
onLearnMoreClick: () -> Unit,
- onOidcDetails: (OidcDetails) -> Unit,
+ onOAuthDetails: (OAuthDetails) -> Unit,
onNeedLoginPassword: () -> Unit,
onCreateAccountContinue: (url: String) -> Unit
) {
@@ -118,7 +118,7 @@ fun LoginModeView(
is AsyncData.Loading -> Unit // The Continue button shows the loading state
is AsyncData.Success -> {
when (val loginModeData = loginMode.data) {
- is LoginMode.Oidc -> onOidcDetails(loginModeData.oidcDetails)
+ is LoginMode.OAuth -> onOAuthDetails(loginModeData.oAuthDetails)
LoginMode.PasswordLogin -> onNeedLoginPassword()
is LoginMode.AccountCreation -> onCreateAccountContinue(loginModeData.url)
}
@@ -137,7 +137,7 @@ internal fun LoginModeViewPreview(@PreviewParameter(LoginModeViewErrorProvider::
loginMode = AsyncData.Failure(error),
onClearError = {},
onLearnMoreClick = {},
- onOidcDetails = {},
+ onOAuthDetails = {},
onNeedLoginPassword = {},
onCreateAccountContinue = {}
)
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt
index 613aa6aeb6..03264551a5 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt
@@ -135,8 +135,8 @@ class QrCodeLoginFlowNode(
is QrLoginException.SlidingSyncNotAvailable -> {
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.SlidingSyncNotAvailable))
}
- is QrLoginException.OidcMetadataInvalid -> {
- Timber.e(error, "OIDC metadata is invalid")
+ is QrLoginException.OAuthMetadataInvalid -> {
+ Timber.e(error, "OAuth metadata is invalid")
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError))
}
QrLoginException.CheckCodeAlreadySent,
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt
index 5dc6ebbd6b..5f79f197d9 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt
@@ -20,7 +20,7 @@ import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.features.login.impl.util.openLearnMorePage
import io.element.android.libraries.architecture.callback
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
@ContributesNode(AppScope::class)
@AssistedInject
@@ -31,7 +31,7 @@ class ChooseAccountProviderNode(
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun navigateToLoginPassword()
- fun navigateToOidc(oidcDetails: OidcDetails)
+ fun navigateToOAuth(oAuthDetails: OAuthDetails)
fun navigateToCreateAccount(url: String)
}
@@ -45,7 +45,7 @@ class ChooseAccountProviderNode(
state = state,
modifier = modifier,
onBackClick = ::navigateUp,
- onOidcDetails = callback::navigateToOidc,
+ onOAuthDetails = callback::navigateToOAuth,
onNeedLoginPassword = callback::navigateToLoginPassword,
onLearnMoreClick = { openLearnMorePage(context) },
onCreateAccountContinue = callback::navigateToCreateAccount,
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt
index cdb80304a7..f05606dbc3 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt
@@ -43,14 +43,14 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.TopAppBar
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun ChooseAccountProviderView(
state: ChooseAccountProviderState,
onBackClick: () -> Unit,
- onOidcDetails: (OidcDetails) -> Unit,
+ onOAuthDetails: (OAuthDetails) -> Unit,
onNeedLoginPassword: () -> Unit,
onLearnMoreClick: () -> Unit,
onCreateAccountContinue: (url: String) -> Unit,
@@ -129,7 +129,7 @@ fun ChooseAccountProviderView(
state.eventSink(ChooseAccountProviderEvents.ClearError)
},
onLearnMoreClick = onLearnMoreClick,
- onOidcDetails = onOidcDetails,
+ onOAuthDetails = onOAuthDetails,
onNeedLoginPassword = onNeedLoginPassword,
onCreateAccountContinue = onCreateAccountContinue,
)
@@ -144,7 +144,7 @@ internal fun ChooseAccountProviderViewPreview(@PreviewParameter(ChooseAccountPro
state = state,
onBackClick = { },
onLearnMoreClick = { },
- onOidcDetails = { },
+ onOAuthDetails = { },
onNeedLoginPassword = { },
onCreateAccountContinue = { },
)
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt
index f2ff998652..cfbd86f363 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt
@@ -31,7 +31,7 @@ import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.appyx.rememberFaderOrSliderTransitionHandler
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.architecture.createNode
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -54,7 +54,7 @@ class ClassicFlowNode(
interface Callback : Plugin {
fun navigateToOnBoarding(allowBackNavigation: Boolean)
fun navigateToLoginPassword()
- fun navigateToOidc(oidcDetails: OidcDetails)
+ fun navigateToOAuth(oAuthDetails: OAuthDetails)
fun navigateToCreateAccount(url: String)
}
@@ -111,8 +111,8 @@ class ClassicFlowNode(
callback.navigateToLoginPassword()
}
- override fun navigateToOidc(oidcDetails: OidcDetails) {
- callback.navigateToOidc(oidcDetails)
+ override fun navigateToOAuth(oAuthDetails: OAuthDetails) {
+ callback.navigateToOAuth(oAuthDetails)
}
override fun navigateToCreateAccount(url: String) {
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicNode.kt
index c42248a3f8..d5acca38ae 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicNode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicNode.kt
@@ -21,7 +21,7 @@ import io.element.android.features.login.impl.util.openLearnMorePage
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.architecture.inputs
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.matrix.api.core.UserId
@ContributesNode(AppScope::class)
@@ -35,7 +35,7 @@ class LoginWithClassicNode(
interface Callback : Plugin {
fun navigateToOtherOptions()
fun navigateToLoginPassword()
- fun navigateToOidc(oidcDetails: OidcDetails)
+ fun navigateToOAuth(oAuthDetails: OAuthDetails)
fun navigateToCreateAccount(url: String)
fun navigateToMissingKeyBackup()
}
@@ -60,7 +60,7 @@ class LoginWithClassicNode(
state = state,
modifier = modifier,
onOtherOptionsClick = callback::navigateToOtherOptions,
- onOidcDetails = callback::navigateToOidc,
+ onOAuthDetails = callback::navigateToOAuth,
onNeedLoginPassword = callback::navigateToLoginPassword,
onLearnMoreClick = { openLearnMorePage(context) },
onCreateAccountContinue = callback::navigateToCreateAccount,
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicStateProvider.kt
index d8dcfeb072..31b1770f63 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicStateProvider.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicStateProvider.kt
@@ -12,13 +12,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.login.impl.login.LoginMode
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.matrix.api.core.UserId
open class LoginWithClassicStateProvider : PreviewParameterProvider {
override val values: Sequence
get() = sequenceOf(
aLoginWithClassicState(),
- aLoginWithClassicState(isElementPro = true, displayName = "Alice"),
+ aLoginWithClassicState(isElementPro = true, displayName = USER_NAME_ALICE),
)
}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt
index 6b5c48f1ec..b1ca50fe61 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt
@@ -49,7 +49,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@@ -59,7 +59,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun LoginWithClassicView(
state: LoginWithClassicState,
onOtherOptionsClick: () -> Unit,
- onOidcDetails: (OidcDetails) -> Unit,
+ onOAuthDetails: (OAuthDetails) -> Unit,
onNeedLoginPassword: () -> Unit,
onLearnMoreClick: () -> Unit,
onCreateAccountContinue: (url: String) -> Unit,
@@ -200,7 +200,7 @@ fun LoginWithClassicView(
state.eventSink(LoginWithClassicEvent.ClearError)
},
onLearnMoreClick = onLearnMoreClick,
- onOidcDetails = onOidcDetails,
+ onOAuthDetails = onOAuthDetails,
onNeedLoginPassword = onNeedLoginPassword,
onCreateAccountContinue = onCreateAccountContinue,
)
@@ -212,7 +212,7 @@ internal fun LoginWithClassicViewPreview(@PreviewParameter(LoginWithClassicState
LoginWithClassicView(
state = state,
onOtherOptionsClick = {},
- onOidcDetails = {},
+ onOAuthDetails = {},
onNeedLoginPassword = {},
onLearnMoreClick = {},
onCreateAccountContinue = {},
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt
index e3643afbf2..928a493dc1 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt
@@ -22,7 +22,7 @@ import io.element.android.features.login.impl.util.openLearnMorePage
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.architecture.inputs
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
@ContributesNode(AppScope::class)
@AssistedInject
@@ -44,7 +44,7 @@ class ConfirmAccountProviderNode(
interface Callback : Plugin {
fun navigateToLoginPassword()
- fun navigateToOidc(oidcDetails: OidcDetails)
+ fun navigateToOAuth(oAuthDetails: OAuthDetails)
fun navigateToCreateAccount(url: String)
fun navigateToChangeAccountProvider()
}
@@ -58,7 +58,7 @@ class ConfirmAccountProviderNode(
ConfirmAccountProviderView(
state = state,
modifier = modifier,
- onOidcDetails = callback::navigateToOidc,
+ onOAuthDetails = callback::navigateToOAuth,
onNeedLoginPassword = callback::navigateToLoginPassword,
onCreateAccountContinue = callback::navigateToCreateAccount,
onChange = callback::navigateToChangeAccountProvider,
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt
index a175ab556d..c2525f3756 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt
@@ -30,7 +30,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.TextButton
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@@ -38,7 +38,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun ConfirmAccountProviderView(
state: ConfirmAccountProviderState,
- onOidcDetails: (OidcDetails) -> Unit,
+ onOAuthDetails: (OAuthDetails) -> Unit,
onNeedLoginPassword: () -> Unit,
onLearnMoreClick: () -> Unit,
onCreateAccountContinue: (url: String) -> Unit,
@@ -103,7 +103,7 @@ fun ConfirmAccountProviderView(
eventSink(ConfirmAccountProviderEvents.ClearError)
},
onLearnMoreClick = onLearnMoreClick,
- onOidcDetails = onOidcDetails,
+ onOAuthDetails = onOAuthDetails,
onNeedLoginPassword = onNeedLoginPassword,
onCreateAccountContinue = onCreateAccountContinue,
)
@@ -117,7 +117,7 @@ internal fun ConfirmAccountProviderViewPreview(
) = ElementPreview {
ConfirmAccountProviderView(
state = state,
- onOidcDetails = {},
+ onOAuthDetails = {},
onNeedLoginPassword = {},
onCreateAccountContinue = {},
onLearnMoreClick = {},
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt
index 5572c412a0..99f7e86fd3 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt
@@ -22,7 +22,7 @@ import io.element.android.features.login.impl.util.openLearnMorePage
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.architecture.inputs
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
@ContributesNode(AppScope::class)
@AssistedInject
@@ -40,7 +40,7 @@ class OnBoardingNode(
fun navigateToQrCode()
fun navigateToBugReport()
fun navigateToLoginPassword()
- fun navigateToOidc(oidcDetails: OidcDetails)
+ fun navigateToOAuth(oAuthDetails: OAuthDetails)
fun navigateToCreateAccount(url: String)
fun navigateToDeveloperSettings()
fun onDone()
@@ -71,7 +71,7 @@ class OnBoardingNode(
onCreateAccount = callback::navigateToSignUpFlow,
onSignInWithQrCode = callback::navigateToQrCode,
onReportProblem = callback::navigateToBugReport,
- onOidcDetails = callback::navigateToOidc,
+ onOAuthDetails = callback::navigateToOAuth,
onNeedLoginPassword = callback::navigateToLoginPassword,
onLearnMoreClick = { openLearnMorePage(context) },
onCreateAccountContinue = callback::navigateToCreateAccount,
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt
index 5ee7ab6ac4..53c36ac4f8 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt
@@ -50,7 +50,7 @@ import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@@ -68,7 +68,7 @@ fun OnBoardingView(
onSignInWithQrCode: () -> Unit,
onSignIn: (mustChooseAccountProvider: Boolean) -> Unit,
onCreateAccount: () -> Unit,
- onOidcDetails: (OidcDetails) -> Unit,
+ onOAuthDetails: (OAuthDetails) -> Unit,
onNeedLoginPassword: () -> Unit,
onLearnMoreClick: () -> Unit,
onCreateAccountContinue: (url: String) -> Unit,
@@ -82,7 +82,7 @@ fun OnBoardingView(
state.eventSink(OnBoardingEvents.ClearError)
},
onLearnMoreClick = onLearnMoreClick,
- onOidcDetails = onOidcDetails,
+ onOAuthDetails = onOAuthDetails,
onNeedLoginPassword = onNeedLoginPassword,
onCreateAccountContinue = onCreateAccountContinue,
)
@@ -354,7 +354,7 @@ internal fun OnBoardingViewPreview(
onSignIn = {},
onCreateAccount = {},
onReportProblem = {},
- onOidcDetails = {},
+ onOAuthDetails = {},
onNeedLoginPassword = {},
onLearnMoreClick = {},
onCreateAccountContinue = {},
diff --git a/features/login/impl/src/main/res/values-be/translations.xml b/features/login/impl/src/main/res/values-be/translations.xml
index 307ccf7263..d9d69503c5 100644
--- a/features/login/impl/src/main/res/values-be/translations.xml
+++ b/features/login/impl/src/main/res/values-be/translations.xml
@@ -24,7 +24,7 @@
"Няправільнае імя карыстальніка і/або пароль"
"Гэта несапраўдны ідэнтыфікатар карыстальніка. Чаканы фармат: ‘@user:homeserver.org’"
"Гэты сервер настроены на выкарыстанне маркераў абнаўлення. Яны не падтрымліваюцца пры ўваходзе на аснове пароля."
- "Выбраны хатні сервер не падтрымлівае пароль або ўваход у OIDC. Калі ласка, звярніцеся да адміністратара або абярыце іншы хатні сервер."
+ "Выбраны хатні сервер не падтрымлівае пароль або ўваход у OAuth. Калі ласка, звярніцеся да адміністратара або абярыце іншы хатні сервер."
"Увядзіце свае даныя"
"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."
"Сардэчна запрашаем!"
diff --git a/features/login/impl/src/main/res/values-bg/translations.xml b/features/login/impl/src/main/res/values-bg/translations.xml
index 6ccc7c9129..c6c74ff1a8 100644
--- a/features/login/impl/src/main/res/values-bg/translations.xml
+++ b/features/login/impl/src/main/res/values-bg/translations.xml
@@ -21,7 +21,7 @@
"Този акаунт бе деактивиран."
"Неправилно потребителско име и/или парола"
"Това не е валиден потребителски идентификатор. Очакван формат: ‘@user:homeserver.org’"
- "Избраният сървър не поддържа влизане с парола или OIDC. Моля, свържете се с вашия администратор или изберете друг сървър."
+ "Избраният сървър не поддържа влизане с парола или OAuth. Моля, свържете се с вашия администратор или изберете друг сървър."
"Въведете своите данни"
"Matrix е отворена мрежа за сигурна, децентрализирана комуникация."
"Добре дошли отново!"
diff --git a/features/login/impl/src/main/res/values-cs/translations.xml b/features/login/impl/src/main/res/values-cs/translations.xml
index 6146f1776d..c5887de6c6 100644
--- a/features/login/impl/src/main/res/values-cs/translations.xml
+++ b/features/login/impl/src/main/res/values-cs/translations.xml
@@ -32,12 +32,20 @@
"Nesprávné uživatelské jméno nebo heslo"
"Toto není platný identifikátor uživatele. Očekávaný formát: \'@user:homeserver.org\'"
"Tento server je nakonfigurován tak, aby používal obnovovací tokeny. Ty nejsou podporovány při použití přihlašovacích údajů založených na hesle."
- "Vybraný domovský server nepodporuje přihlášení pomocí hesla nebo OIDC. Kontaktujte prosím svého správce nebo vyberte jiný domovský server."
+ "Vybraný domovský server nepodporuje přihlášení pomocí hesla nebo OAuth. Kontaktujte prosím svého správce nebo vyberte jiný domovský server."
"Zadejte své údaje"
"Matrix je otevřená síť pro bezpečnou a decentralizovanou komunikaci."
"Vítejte zpět!"
"Přihlaste se k %1$s"
+ "Otevřít Element Classic"
+ "Otevřete Element Classic na svém zařízení"
+ "Přejděte do Nastavení > Zabezpečení a soukromí"
+ "V části Správa kryptografických klíčů vyberte Obnova šifrovaných zpráv"
+ "Postupujte podle pokynů k povolení úložiště klíčů"
+ "Vraťte se do %1$s"
+ "Povolte úložiště klíčů, než budete pokračovat na %1$s"
"Verze %1$s"
+ "Kontrola účtu"
"Ruční přihlášení"
"Přihlaste se k %1$s"
"Přihlásit se pomocí QR kódu"
diff --git a/features/login/impl/src/main/res/values-cy/translations.xml b/features/login/impl/src/main/res/values-cy/translations.xml
index b8988a9889..0f44287ed4 100644
--- a/features/login/impl/src/main/res/values-cy/translations.xml
+++ b/features/login/impl/src/main/res/values-cy/translations.xml
@@ -32,7 +32,7 @@
"Enw defnyddiwr a/neu gyfrinair anghywir"
"Nid yw hwn yn ddynodwr defnyddiwr dilys. Fformat disgwyliedig: ‘@user:homeserver.org’"
"Mae\'r gweinydd hwn wedi\'i ffurfweddu i ddefnyddio tocynnau adnewyddu. Nid yw\'r rhain yn cael eu cefnogi wrth ddefnyddio mewngofnodi ar sail cyfrinair."
- "Nid yw\'r gweinydd cartref ddewiswyd yn cefnogi cyfrinair na mewngofnodi OIDC. Cysylltwch â\'ch gweinyddwr neu dewis gweinydd cartref arall."
+ "Nid yw\'r gweinydd cartref ddewiswyd yn cefnogi cyfrinair na mewngofnodi OAuth. Cysylltwch â\'ch gweinyddwr neu dewis gweinydd cartref arall."
"Rhowch eich manylion"
"Mae Matrix yn rhwydwaith agored ar gyfer cyfathrebu diogel, datganoledig."
"Croeso nôl!"
diff --git a/features/login/impl/src/main/res/values-da/translations.xml b/features/login/impl/src/main/res/values-da/translations.xml
index 284284cc3e..35d66a6e69 100644
--- a/features/login/impl/src/main/res/values-da/translations.xml
+++ b/features/login/impl/src/main/res/values-da/translations.xml
@@ -28,16 +28,24 @@
"Hvad er adressen på din server?"
"Vælg din server"
"Opret konto"
- "Denne konto er blevet deaktiveret."
+ "Denne konto er blevet slettet."
"Forkert brugernavn og/eller adgangskode"
"Dette er ikke en gyldig brugeridentifikation. Forventet format: \'@bruger:hjemmeserver.org\'"
"Denne server er konfigureret til at bruge opdateringstokens. Disse understøttes ikke, når du bruger adgangskodebaseret login."
- "Den valgte hjemmeserver understøtter ikke adgangskode eller OIDC-login. Kontakt venligst din administrator eller vælg en anden hjemmeserver."
+ "Den valgte hjemmeserver understøtter ikke adgangskode eller OAuth-login. Kontakt venligst din administrator eller vælg en anden hjemmeserver."
"Indtast dine oplysninger"
"Matrix er et åbent netværk for sikker, decentraliseret kommunikation."
"Velkommen tilbage!"
"Log ind på %1$s"
+ "Åbn Element Classic"
+ "Åbn Element Classic på din enhed"
+ "Gå til Indstillinger > Sikkerhed og privatliv"
+ "I Nøgleadministration skal du, under Kryptografi, vælge Gendannelse af krypterede meddelelser"
+ "Følg instruktionerne for at aktivere dit nøglelager"
+ "Gå tilbage til %1$s"
+ "Aktivér dit nøglelager, før du fortsætter til %1$s"
"Version %1$s"
+ "Kontoen kontrolleres…"
"Log ind manuelt"
"Log ind på %1$s"
"Log ind med QR-kode"
diff --git a/features/login/impl/src/main/res/values-de/translations.xml b/features/login/impl/src/main/res/values-de/translations.xml
index dced91a1c9..89e4412d0f 100644
--- a/features/login/impl/src/main/res/values-de/translations.xml
+++ b/features/login/impl/src/main/res/values-de/translations.xml
@@ -28,11 +28,11 @@
"Wie lautet die Adresse deines Servers?"
"Wähle deinen Server aus"
"Konto erstellen"
- "Dieses Konto wurde deaktiviert."
+ "Dieses Konto wurde gelöscht."
"Falscher Nutzername und/oder Passwort"
"Dies ist keine gültige Nutzerkennung. Erwartetes Format: \'@nutzer:homeserver.org\'"
"Dieser Server ist so konfiguriert, dass er Refresh-Tokens verwendet. Diese werden für die passwortbasierte Anmeldung nicht unterstützt."
- "Der ausgewählte Homeserver unterstützt weder den Login per Passwort noch per OIDC. Bitte kontaktiere deinen Administrator oder wähle einen anderen Homeserver."
+ "Der ausgewählte Homeserver unterstützt weder den Login per Passwort noch per OAuth. Bitte kontaktiere deinen Administrator oder wähle einen anderen Homeserver."
"Gib deine Daten ein"
"Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation."
"Willkommen zurück!"
diff --git a/features/login/impl/src/main/res/values-el/translations.xml b/features/login/impl/src/main/res/values-el/translations.xml
index c87027477d..85640698f3 100644
--- a/features/login/impl/src/main/res/values-el/translations.xml
+++ b/features/login/impl/src/main/res/values-el/translations.xml
@@ -32,7 +32,7 @@
"Λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης"
"Αυτό δεν είναι έγκυρο αναγνωριστικό χρήστη. Αναμενόμενη μορφή: \'@χρήστης:homeserver.org\'"
"Αυτός ο διακομιστής έχει ρυθμιστεί ώστε να χρησιμοποιεί διακριτικά ανανέωσης. Αυτά δεν υποστηρίζονται όταν χρησιμοποιείς σύνδεση μέσω κωδικού πρόσβασης."
- "Ο επιλεγμένος οικιακός διακομιστής δεν υποστηρίζει κωδικό πρόσβασης ή σύνδεση OIDC. Επικοινωνήστε με τον διαχειριστή σου ή επέλεξε άλλο οικιακό διακομιστή."
+ "Ο επιλεγμένος οικιακός διακομιστής δεν υποστηρίζει κωδικό πρόσβασης ή σύνδεση OAuth. Επικοινωνήστε με τον διαχειριστή σου ή επέλεξε άλλο οικιακό διακομιστή."
"Εισήγαγε τα στοιχεία σου"
"Το Matrix είναι ένα ανοιχτό δίκτυο για ασφαλή, αποκεντρωμένη επικοινωνία."
"Καλωσόρισες ξανά!"
diff --git a/features/login/impl/src/main/res/values-es/translations.xml b/features/login/impl/src/main/res/values-es/translations.xml
index df6a06ab29..3b9ffbe2dd 100644
--- a/features/login/impl/src/main/res/values-es/translations.xml
+++ b/features/login/impl/src/main/res/values-es/translations.xml
@@ -29,7 +29,7 @@
"Usuario y/o contraseña incorrectos"
"Este no es un id de usuario válido. Formato esperado: \'@user:homeserver.org\'"
"Este servidor está configurado para utilizar tokens de actualización. Estos no son compatibles cuando se utiliza el inicio de sesión basado en contraseña."
- "El servidor base seleccionado no admite el inicio de sesión usando contraseña ni OIDC. Ponte en contacto con tu administrador o elige otro servidor base."
+ "El servidor base seleccionado no admite el inicio de sesión usando contraseña ni OAuth. Ponte en contacto con tu administrador o elige otro servidor base."
"Introduce tus datos"
"Matrix es una red abierta para una comunicación segura y descentralizada."
"¡Hola de nuevo!"
diff --git a/features/login/impl/src/main/res/values-et/translations.xml b/features/login/impl/src/main/res/values-et/translations.xml
index 7ac4e99694..b0b60009d4 100644
--- a/features/login/impl/src/main/res/values-et/translations.xml
+++ b/features/login/impl/src/main/res/values-et/translations.xml
@@ -32,7 +32,7 @@
"Vigane kasutajanimi ja/või salasõna"
"See ei ole korrektne kasutajanimi. Õige vorming on: „@kasutaja:koduserver.ee“"
"See server on seadistatud kasutama tunnusloa põhist sisselogimist. Salasõnaga sisselogimisel see võimalus aga ei ole toetatud."
- "Valitud koduserver ei toeta salasõna ega OIDC-põhist sisselogimist. Lisateavet saad koduserveri haldajalt, aga sa võid ka valida mõne teise serveri."
+ "Valitud koduserver ei toeta salasõna ega OAuth-põhist sisselogimist. Lisateavet saad koduserveri haldajalt, aga sa võid ka valida mõne teise serveri."
"Sisesta oma andmed"
"Matrix on avatud võrk turvalise ja hajutatud suhtluse jaoks."
"Tere tulemast tagasi!"
diff --git a/features/login/impl/src/main/res/values-eu/translations.xml b/features/login/impl/src/main/res/values-eu/translations.xml
index 355e63546c..78a13e5810 100644
--- a/features/login/impl/src/main/res/values-eu/translations.xml
+++ b/features/login/impl/src/main/res/values-eu/translations.xml
@@ -21,7 +21,7 @@
"Sortu kontua"
"Kontu hau desaktibatuta dago."
"Erabiltzaile-izena edo/eta pasahitza okerrak"
- "Hautatutako zerbitzaria ez da bateragarria pasahitz edo OIDC saio-hasierarekin. Jarri harremanetan administratzailearekin edo aukeratu beste zerbitzari bat."
+ "Hautatutako zerbitzaria ez da bateragarria pasahitz edo OAuth saio-hasierarekin. Jarri harremanetan administratzailearekin edo aukeratu beste zerbitzari bat."
"Sartu zure datuak"
"Matrix komunikazio seguru eta deszentralizaturako sare irekia da."
"Ongi etorri!"
diff --git a/features/login/impl/src/main/res/values-fa/translations.xml b/features/login/impl/src/main/res/values-fa/translations.xml
index ef7062a88a..d903103c1b 100644
--- a/features/login/impl/src/main/res/values-fa/translations.xml
+++ b/features/login/impl/src/main/res/values-fa/translations.xml
@@ -20,10 +20,10 @@
"نشانی کارسازتان چیست؟"
"کارسازتان را برگزینید"
"ایجاد حساب"
- "این حساب از کار افتاده است."
+ "این حساب حذف شده است."
"نام کاربری یا گذرواژه نامعتبر است"
"این یک شناسه کاربری معتبر نیست. قالب صحیح: «@user:homeserver.or"
- "کارساز اصلی انتخاب شده از رمز عبور یا ورود OIDC پشتیبانی نمی کند. لطفا با مدیر خود تماس بگیرید یا یک کارساز خانگی دیگر را انتخاب کنید."
+ "کارساز اصلی انتخاب شده از رمز عبور یا ورود OAuth پشتیبانی نمی کند. لطفا با مدیر خود تماس بگیرید یا یک کارساز خانگی دیگر را انتخاب کنید."
"جزییاتتان را وارد کنید"
"ماتریکس شبکهای بار برای ارتباطات نامتمرکز و امن است."
"خوش برگشتید!"
diff --git a/features/login/impl/src/main/res/values-fi/translations.xml b/features/login/impl/src/main/res/values-fi/translations.xml
index 0d795811b1..4f5225be67 100644
--- a/features/login/impl/src/main/res/values-fi/translations.xml
+++ b/features/login/impl/src/main/res/values-fi/translations.xml
@@ -32,7 +32,7 @@
"Väärä käyttäjänimi ja/tai salasana"
"Tämä ei ole kelvollinen käyttäjätunnus. Odotettu muoto: \'@käyttäjä:kotipalvelin.fi\'"
"Tämä palvelin on määritetty käyttämään refresh tokeneja. Näitä ei tueta salasanapohjaisen kirjautumisen kanssa."
- "Valitsemasi kotipalvelin ei tue salasana- tai OIDC-kirjautumista. Ota yhteyttä palvelimesi ylläpitäjään tai valitse toinen kotipalvelin."
+ "Valitsemasi kotipalvelin ei tue salasana- tai OAuth-kirjautumista. Ota yhteyttä palvelimesi ylläpitäjään tai valitse toinen kotipalvelin."
"Anna tietosi"
"Matrix on avoin verkko turvallista, hajautettua viestintää varten."
"Tervetuloa takaisin!"
diff --git a/features/login/impl/src/main/res/values-fr/translations.xml b/features/login/impl/src/main/res/values-fr/translations.xml
index 3435eb7d40..504517aad7 100644
--- a/features/login/impl/src/main/res/values-fr/translations.xml
+++ b/features/login/impl/src/main/res/values-fr/translations.xml
@@ -28,11 +28,11 @@
"Quelle est l’adresse de votre serveur ?"
"Choisissez votre serveur"
"Créer un compte"
- "Ce compte a été désactivé."
+ "Ce compte a été supprimé."
"Nom d’utilisateur et/ou mot de passe incorrects"
"Il ne s’agit pas d’un identifiant utilisateur valide. Format attendu : « @user:homeserver.org »"
"Ce serveur est configuré pour utiliser des tokens d’actualisation. Ils ne sont pas pris en charge lors de l’utilisation d’une connexion basée sur un mot de passe."
- "Le serveur d’accueil sélectionné ne prend pas en charge le mot de passe ou la connexion OIDC. Contactez votre administrateur ou choisissez un autre serveur d’accueil."
+ "Le serveur d’accueil sélectionné ne prend pas en charge le mot de passe ou la connexion OAuth. Contactez votre administrateur ou choisissez un autre serveur d’accueil."
"Saisissez vos identifiants"
"Matrix est un réseau ouvert pour une communication sécurisée et décentralisée."
"Content de vous revoir !"
diff --git a/features/login/impl/src/main/res/values-hr/translations.xml b/features/login/impl/src/main/res/values-hr/translations.xml
index b52dd77d7f..f578790380 100644
--- a/features/login/impl/src/main/res/values-hr/translations.xml
+++ b/features/login/impl/src/main/res/values-hr/translations.xml
@@ -32,7 +32,7 @@
"Netočno korisničko ime i/ili zaporka"
"To nije valjani identifikator korisnika. Očekivani oblik: ‘@korisnik:matičniposlužitelj.org’"
"Ovaj je poslužitelj konfiguriran za korištenje tokena za osvježavanje. Oni nisu podržani kada se upotrebljava prijava temeljena na zaporki."
- "Odabrani matični poslužitelj ne podržava zaporku ili OIDC prijavu. Obratite se administratoru ili odaberite drugi matični poslužitelj."
+ "Odabrani matični poslužitelj ne podržava zaporku ili OAuth prijavu. Obratite se administratoru ili odaberite drugi matični poslužitelj."
"Unesite svoje podatke"
"Matrix je otvorena mreža za sigurnu, decentraliziranu komunikaciju."
"Dobro došli natrag!"
diff --git a/features/login/impl/src/main/res/values-hu/translations.xml b/features/login/impl/src/main/res/values-hu/translations.xml
index c2b2fefddb..26534cc523 100644
--- a/features/login/impl/src/main/res/values-hu/translations.xml
+++ b/features/login/impl/src/main/res/values-hu/translations.xml
@@ -28,11 +28,11 @@
"Mi a kiszolgálója címe?"
"Válassza ki a kiszolgálóját"
"Fiók létrehozása"
- "Ez a fiók deaktiválva lett."
+ "Ez a fiók törölve lett."
"Helytelen felhasználónév vagy jelszó"
"Ez nem érvényes felhasználóazonosító. A várt formátum: „@user:homeserver.org”"
"Ez a kiszolgáló frissítési tokenek használatára van beállítva. Ezek jelszó alapú bejelentkezés esetén nem támogatottak."
- "A kiválasztott Matrix-kiszolgáló nem támogatja a jelszavas vagy OIDC-alapú bejelentkezést. Lépjen kapcsolatba a kiszolgáló adminisztrátorával, vagy válasszon másik Matrix-kiszolgálót."
+ "A kiválasztott Matrix-kiszolgáló nem támogatja a jelszavas vagy OAuth-alapú bejelentkezést. Lépjen kapcsolatba a kiszolgáló adminisztrátorával, vagy válasszon másik Matrix-kiszolgálót."
"Adja meg adatait"
"A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz."
"Örülünk, hogy visszatért!"
diff --git a/features/login/impl/src/main/res/values-in/translations.xml b/features/login/impl/src/main/res/values-in/translations.xml
index e05fd8746d..4b9f3ffdef 100644
--- a/features/login/impl/src/main/res/values-in/translations.xml
+++ b/features/login/impl/src/main/res/values-in/translations.xml
@@ -32,7 +32,7 @@
"Nama pengguna dan/atau kata sandi salah"
"Ini bukan pengenal pengguna yang valid. Format yang diharapkan: \'@pengguna:homeserver.org\'"
"Server ini diatur untuk menggunakan token penyegaran. Ini tidak didukung ketika menggunakan log masuk berbasis kata sandi."
- "Homeserver yang dipilih tidak mendukung log masuk kata sandi atau OIDC. Silakan hubungi admin Anda atau pilih homeserver yang lain."
+ "Homeserver yang dipilih tidak mendukung log masuk kata sandi atau OAuth. Silakan hubungi admin Anda atau pilih homeserver yang lain."
"Masukkan detail Anda"
"Matrix adalah jaringan terbuka untuk komunikasi yang aman dan terdesentralisasi."
"Selamat datang kembali!"
diff --git a/features/login/impl/src/main/res/values-it/translations.xml b/features/login/impl/src/main/res/values-it/translations.xml
index e882654e4b..3753d58390 100644
--- a/features/login/impl/src/main/res/values-it/translations.xml
+++ b/features/login/impl/src/main/res/values-it/translations.xml
@@ -32,7 +32,7 @@
"Nome utente e/o password errati"
"Questo non è un identità utente valida. il formato atteso é: \'@user:homeserver.org\'"
"Questo server è configurato per usare i token di aggiornamento. Non sono supportati quando si usa l\'accesso basato su password."
- "L\'homeserver selezionato non supporta la password o l\'accesso OIDC. Contatta il tuo amministratore o scegli un altro homeserver."
+ "L\'homeserver selezionato non supporta la password o l\'accesso OAuth. Contatta il tuo amministratore o scegli un altro homeserver."
"Inserisci i tuoi dati"
"Matrix è una rete aperta per comunicazioni sicure e decentralizzate."
"Bentornato!"
diff --git a/features/login/impl/src/main/res/values-ja/translations.xml b/features/login/impl/src/main/res/values-ja/translations.xml
index 219605ab22..42698e27dc 100644
--- a/features/login/impl/src/main/res/values-ja/translations.xml
+++ b/features/login/impl/src/main/res/values-ja/translations.xml
@@ -1,18 +1,18 @@
- "アカウントの提供元を変更"
+ "アカウント提供元を変更"
"ホームサーバーのアドレス"
- "検索用のキーワードまたはドメインのアドレスを入力してください。"
+ "検索のキーワードまたはドメインのアドレスを入力してください。"
"会社やコミュニティ, 個人のサーバーなどを検索します。"
- "アカウントの提供元を検索"
- "メールアプリのように、あなたの会話はここに保管されています。"
+ "アカウント提供元を検索"
+ "メールアプリのように、あなたの会話はこのサーバー上に保管されます。"
"%s にサインインを試みています"
- "メールアプリのように、あなたの会話はここに保管されています。"
- "%s にアカウントの作成を試みています"
+ "メールアプリのように、あなたの会話はこのサーバー上に保管されます。"
+ "%s 上にアカウントを作成しようとしています"
"Matrix.org は Matrix.org Foundation が運営する、大規模で安全な分散型コミュニケーションを実現する無償のサーバーです。"
"その他"
- "自身のサーバーや仕事用のアカウントにサインインするには、アカウント提供元のサーバーを指定してください。"
- "アカウントの提供元を変更"
+ "自身のサーバーや仕事用のアカウントにサインインするには、アカウント提供元を変更してください。"
+ "アカウント提供元を変更"
"Google Play"
"%1$s では Element Pro を使用する必要があります。アプリストアよりダウンロードしてください。"
"Element Pro が必要です"
@@ -27,11 +27,11 @@
"サーバーのアドレスは何ですか?"
"サーバーを選択"
"アカウントを作成"
- "このアカウントは無効化されています。"
+ "アカウントは削除されました。"
"ユーザー名またはパスワードが違います"
"無効なユーザーIDです。正しい形式は \"@ユーザー:ホームサーバー\" です。"
"このサーバーはリフレッシュトークンを使用します。パスワードを使用したログインとは併用できません。"
- "指定したホームサーバはパスワードまたはOIDCによるログインに対応していません。管理者に問い合わせるか、異なるホームサーバーを使用してください。"
+ "指定したホームサーバはパスワードまたはOAuthによるログインに対応していません。管理者に問い合わせるか、異なるホームサーバーを使用してください。"
"詳細を入力"
"Matrix は安全で分散型のオープンなネットワークです。"
"お待ちしておりました。"
@@ -80,8 +80,8 @@
"%1$s に非対応"
"読み取る"
"コンピュータで %1$s を開く"
- "アバターをタップしてください"
- "%1$s を選択してください"
+ "アバターをタップ"
+ "%1$s を選択"
"\"新しい端末を追加\""
"この端末でQRコードを読み取る"
"アカウント提供元が対応する場合にのみ使用できます。"
@@ -98,11 +98,11 @@
"一方の端末を待機しています"
"アカウント提供元が、サインインを検証するために以下の文字列を要求することがあります。"
"検証コード"
- "アカウントの提供元を変更"
+ "アカウント提供元を変更"
"Element 開発者用の非公開のサーバーです。"
"Matrix は安全で分散型のオープンなネットワークです。"
- "メールアプリのように、あなたの会話はここに保管されています。"
+ "メールアプリのように、あなたの会話はこのサーバー上に保管されます。"
"%1$s にサインインを試みています"
"アカウント提供元を選択"
- "%1$s 上にアカウントの作成を試みています"
+ "%1$s 上にアカウントを作成しようとしています"
diff --git a/features/login/impl/src/main/res/values-ka/translations.xml b/features/login/impl/src/main/res/values-ka/translations.xml
index 54e277ec12..04db3fda13 100644
--- a/features/login/impl/src/main/res/values-ka/translations.xml
+++ b/features/login/impl/src/main/res/values-ka/translations.xml
@@ -23,7 +23,7 @@
"არასწორი მომხმარებლის სახელი და/ან პაროლი"
"მოცემული მომხმარებლის იდენტიფიკატორი არასწორია. დასაშვები ფორმატი: ‘@user:homeserver.org’"
"ეს სერვერი კონფიგურირებულია განახლების გასაღებების გამოსაყენებლად. პაროლზე დაფუძნებული შეცვლისას ისინი მხარდაჭერილი არაა."
- "მოცემული სახლის სერვერი მხარს არ უჭერს პაროლით ან OIDC-ით შესვლას. გთხოვთ, დაუკავშირდეთ თქვენს ადმინისტრატორს ან აარჩიეთ სხვა სახლის სერვერი."
+ "მოცემული სახლის სერვერი მხარს არ უჭერს პაროლით ან OAuth-ით შესვლას. გთხოვთ, დაუკავშირდეთ თქვენს ადმინისტრატორს ან აარჩიეთ სხვა სახლის სერვერი."
"შეიყვანეთ თქვენი დეტალები"
"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."
"კეთილი იყოს თქვენი მობრძანება!"
diff --git a/features/login/impl/src/main/res/values-ko/translations.xml b/features/login/impl/src/main/res/values-ko/translations.xml
index c870377fc4..338ba628c9 100644
--- a/features/login/impl/src/main/res/values-ko/translations.xml
+++ b/features/login/impl/src/main/res/values-ko/translations.xml
@@ -32,7 +32,7 @@
"잘못된 아이디/비밀번호"
"이 사용자 ID는 유효하지 않습니다. 예상 형식: ‘@user:homeserver.org’"
"이 서버는 새로 고침 토큰을 사용하도록 구성되어 있습니다. 비밀번호 기반 로그인을 사용하는 경우 이 기능은 지원되지 않습니다."
- "선택한 홈 서버는 password 또는 OIDC 로그인을 지원하지 않습니다. 관리자에게 문의하거나 다른 홈 서버를 선택하세요."
+ "선택한 홈 서버는 password 또는 OAuth 로그인을 지원하지 않습니다. 관리자에게 문의하거나 다른 홈 서버를 선택하세요."
"귀하의 세부 정보를 입력하십시오"
"Matrix 는 안전하고 분산된 커뮤니케이션을 위한 개방형 네트워크입니다."
"다시 돌아온 걸 환영합니다!"
diff --git a/features/login/impl/src/main/res/values-lt/translations.xml b/features/login/impl/src/main/res/values-lt/translations.xml
index 870e2c4d31..dc1a2b7ba6 100644
--- a/features/login/impl/src/main/res/values-lt/translations.xml
+++ b/features/login/impl/src/main/res/values-lt/translations.xml
@@ -31,7 +31,7 @@
"Ši paskyra buvo išjungta."
"Neteisingas vartotojo vardas ir (arba) slaptažodis"
"Tai nėra tinkamas vartotojo vardas. Reikalingas formatas: \'@vartotojas:serveris.org\'"
- "Pasirinktas serveris nepalaiko slaptažodžio ar OIDC prisijungimo. Susisiekite su serverio administracija arba pasirinkite kitą serverį."
+ "Pasirinktas serveris nepalaiko slaptažodžio ar OAuth prisijungimo. Susisiekite su serverio administracija arba pasirinkite kitą serverį."
"Įveskite savo duomenis"
"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."
"Sveiki sugrįžę!"
diff --git a/features/login/impl/src/main/res/values-nb/translations.xml b/features/login/impl/src/main/res/values-nb/translations.xml
index 5957b902ce..6645691b84 100644
--- a/features/login/impl/src/main/res/values-nb/translations.xml
+++ b/features/login/impl/src/main/res/values-nb/translations.xml
@@ -32,7 +32,7 @@
"Feil brukernavn og/eller passord"
"Dette er ikke en gyldig brukeridentifikator. Forventet format: \'@bruker:homeserver.org\'"
"Denne serveren er konfigurert til å bruke oppdateringstokener. Disse støttes ikke når du bruker passordbasert pålogging."
- "Den valgte hjemmeserveren støtter ikke passord eller OIDC-pålogging. Ta kontakt med administratoren din eller velg en annen hjemmeserver."
+ "Den valgte hjemmeserveren støtter ikke passord eller OAuth-pålogging. Ta kontakt med administratoren din eller velg en annen hjemmeserver."
"Skriv inn opplysningene dine"
"Matrix er et åpent nettverk for sikker, desentralisert kommunikasjon."
"Velkommen tilbake!"
diff --git a/features/login/impl/src/main/res/values-nl/translations.xml b/features/login/impl/src/main/res/values-nl/translations.xml
index 8a7f695156..e5d90e2234 100644
--- a/features/login/impl/src/main/res/values-nl/translations.xml
+++ b/features/login/impl/src/main/res/values-nl/translations.xml
@@ -24,7 +24,7 @@
"Onjuiste gebruikersnaam en/of wachtwoord"
"Dit is geen geldige gebruikers-ID. Verwacht formaat: \'@user:homeserver.org\'"
"Deze server is geconfigureerd om verversingstokens te gebruiken. Deze worden niet ondersteund bij inloggen met een wachtwoord."
- "De geselecteerde homeserver ondersteunt geen wachtwoord of OIDC aanmelding. Neem contact op met je beheerder of kies een andere homeserver."
+ "De geselecteerde homeserver ondersteunt geen wachtwoord of OAuth aanmelding. Neem contact op met je beheerder of kies een andere homeserver."
"Vul je gegevens in"
"Matrix is een open netwerk voor veilige, gedecentraliseerde communicatie."
"Welkom terug!"
diff --git a/features/login/impl/src/main/res/values-pl/translations.xml b/features/login/impl/src/main/res/values-pl/translations.xml
index 0c7810abe7..b134395adf 100644
--- a/features/login/impl/src/main/res/values-pl/translations.xml
+++ b/features/login/impl/src/main/res/values-pl/translations.xml
@@ -32,7 +32,7 @@
"Nieprawidłowa nazwa użytkownika i/lub hasło"
"To nie jest prawidłowy identyfikator użytkownika. Oczekiwany format: \'@user:homeserver.org\'"
"Ten serwer został skonfigurowany do korzystania z tokenów odświeżania. Nie są one obsługiwane, gdy korzystasz z hasła."
- "Wybrany serwer domowy nie obsługuje uwierzytelniania hasłem, ani OIDC. Skontaktuj się z jego administratorem lub wybierz inny serwer domowy."
+ "Wybrany serwer domowy nie obsługuje uwierzytelniania hasłem, ani OAuth. Skontaktuj się z jego administratorem lub wybierz inny serwer domowy."
"Wprowadź swoje dane"
"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."
"Witaj ponownie!"
diff --git a/features/login/impl/src/main/res/values-pt-rBR/translations.xml b/features/login/impl/src/main/res/values-pt-rBR/translations.xml
index afca14d201..ad2ad2b5fb 100644
--- a/features/login/impl/src/main/res/values-pt-rBR/translations.xml
+++ b/features/login/impl/src/main/res/values-pt-rBR/translations.xml
@@ -32,7 +32,7 @@
"Nome de usuário e/ou senha incorretos"
"Esse não é um identificador de usuário válido. Formato esperado: \'@usuário:servidor.org\'"
"Este servidor está configurado para usar tokens recarregados. Não há suporte a eles ao entrar por uma senha."
- "O servidor selecionado não suporta a entrada por senha ou OIDC. Entre em contato com o administrador ou escolha outro servidor."
+ "O servidor selecionado não suporta a entrada por senha ou OAuth. Entre em contato com o administrador ou escolha outro servidor."
"Digite seus dados"
"A Matrix é uma rede aberta para comunicação segura e descentralizada."
"Boas-vindas novamente!"
diff --git a/features/login/impl/src/main/res/values-pt/translations.xml b/features/login/impl/src/main/res/values-pt/translations.xml
index c2aa0d5ed6..ff3cae843d 100644
--- a/features/login/impl/src/main/res/values-pt/translations.xml
+++ b/features/login/impl/src/main/res/values-pt/translations.xml
@@ -28,11 +28,11 @@
"Qual é o endereço do teu servidor?"
"Seleciona o teu servidor"
"Criar conta"
- "Esta conta foi desativada."
+ "Esta conta foi eliminada."
"Nome de utilizador ou senha incorretos"
"Identificador de utilizador inválido. Formato esperado: ‘@utilizador:servidor.org’"
"Este servidor está configurado para utilizar \"tokens\" de atualização. Estes não são suportados quando utilizas o início de sessão por senha."
- "O servidor selecionado não suporta início de sessão por senha nem por OIDC. Por favor, contacta o teu administrador ou escolhe outro servidor."
+ "O servidor selecionado não suporta início de sessão por senha nem por OAuth. Por favor, contacta o teu administrador ou escolhe outro servidor."
"Insere o teus detalhes"
"A Matrix é uma rede aberta de comunicação descentralizada e segura."
"Bem-vindo(a) de volta!"
diff --git a/features/login/impl/src/main/res/values-ro/translations.xml b/features/login/impl/src/main/res/values-ro/translations.xml
index 6dddc4d0bf..ec9b260208 100644
--- a/features/login/impl/src/main/res/values-ro/translations.xml
+++ b/features/login/impl/src/main/res/values-ro/translations.xml
@@ -32,7 +32,7 @@
"Utilizator și/sau parolă incorecte"
"Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”"
"Acest server este configurat pentru a utiliza token-uri de reîmprospătare. Acestea nu sunt acceptate atunci când utilizați autentificare bazată pe parolă."
- "Homeserver-ul selectat nu acceptă autentificarea prin parola sau OIDC. Te rugăm să contactezi administratorul sau să alegi un alt homeserver."
+ "Homeserver-ul selectat nu acceptă autentificarea prin parola sau OAuth. Te rugăm să contactezi administratorul sau să alegi un alt homeserver."
"Introduceți detaliile"
"Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată."
"Bine ați revenit!"
diff --git a/features/login/impl/src/main/res/values-ru/translations.xml b/features/login/impl/src/main/res/values-ru/translations.xml
index 4437c19056..329bad30af 100644
--- a/features/login/impl/src/main/res/values-ru/translations.xml
+++ b/features/login/impl/src/main/res/values-ru/translations.xml
@@ -32,7 +32,7 @@
"Неверное имя пользователя и/или пароль"
"Это некорректный идентификатор пользователя. Правильный формат: @user:homeserver.org"
"Этот сервер настроен на использование токенов обновления. Они не поддерживаются при использовании входа на основе пароля."
- "Выбранный сервер не поддерживает вход по паролю и OIDC. Пожалуйста, свяжитесь с администратором или выберите другой сервер."
+ "Выбранный сервер не поддерживает вход по паролю и OAuth. Пожалуйста, свяжитесь с администратором или выберите другой сервер."
"Введите свои данные"
"Matrix — это открытая сеть для безопасной децентрализованной связи."
"Рады видеть вас снова!"
diff --git a/features/login/impl/src/main/res/values-sk/translations.xml b/features/login/impl/src/main/res/values-sk/translations.xml
index 9bd494dd56..b2c3077de2 100644
--- a/features/login/impl/src/main/res/values-sk/translations.xml
+++ b/features/login/impl/src/main/res/values-sk/translations.xml
@@ -32,7 +32,7 @@
"Nesprávne používateľské meno a/alebo heslo"
"Toto nie je platný identifikátor používateľa. Očakávaný formát: \'@pouzivatel:homeserver.sk\'"
"Tento server je nakonfigurovaný tak, aby používal obnovovacie tokeny. Pri prihlasovaní na základe hesla nie sú podporované."
- "Vybraný domovský server nepodporuje prihlásenie pomocou hesla alebo OIDC. Obráťte sa na správcu alebo vyberte iný domovský server."
+ "Vybraný domovský server nepodporuje prihlásenie pomocou hesla alebo OAuth. Obráťte sa na správcu alebo vyberte iný domovský server."
"Zadajte svoje údaje"
"Matrix je otvorená sieť pre bezpečnú a decentralizovanú komunikáciu."
"Vitajte späť!"
diff --git a/features/login/impl/src/main/res/values-sv/translations.xml b/features/login/impl/src/main/res/values-sv/translations.xml
index 33fb76b5bd..396f2025b7 100644
--- a/features/login/impl/src/main/res/values-sv/translations.xml
+++ b/features/login/impl/src/main/res/values-sv/translations.xml
@@ -32,7 +32,7 @@
"Felaktigt användarnamn och/eller lösenord"
"Detta är inte en giltig användaridentifierare. Förväntat format: \'@användare:hemserver.org\'"
"Den här servern är konfigurerad för att använda uppdateringstokens. Dessa stöds inte när du använder lösenordsbaserad inloggning."
- "Den valda hemservern stöder inte lösenord eller OIDC-inloggning. Kontakta administratören eller välj en annan hemserver."
+ "Den valda hemservern stöder inte lösenord eller OAuth-inloggning. Kontakta administratören eller välj en annan hemserver."
"Ange dina uppgifter"
"Matrix är ett öppet nätverk för säker, decentraliserad kommunikation."
"Välkommen tillbaka!"
diff --git a/features/login/impl/src/main/res/values-tr/translations.xml b/features/login/impl/src/main/res/values-tr/translations.xml
index 1574fca3a8..05bb9bab15 100644
--- a/features/login/impl/src/main/res/values-tr/translations.xml
+++ b/features/login/impl/src/main/res/values-tr/translations.xml
@@ -28,7 +28,7 @@
"Yanlış kullanıcı adı ve/veya şifre"
"Bu geçerli bir kullanıcı tanımlayıcısı değil. Kullanılması gereken biçim: \'@user:homeserver.org\'"
"Bu sunucu, yenileme belirteçlerini kullanacak şekilde yapılandırılmıştır. Parola tabanlı oturum açma kullanılırken bunlar desteklenmez."
- "Seçilen ana sunucu parola veya OIDC oturum açmayı desteklemiyor. Lütfen yöneticinizle iletişime geçin veya başka bir ana sunucu seçin."
+ "Seçilen ana sunucu parola veya OAuth oturum açmayı desteklemiyor. Lütfen yöneticinizle iletişime geçin veya başka bir ana sunucu seçin."
"Bilgilerinizi girin"
"Matrix, güvenli, merkezi olmayan iletişim için açık bir ağdır."
"Tekrar hoş geldiniz!"
diff --git a/features/login/impl/src/main/res/values-uk/translations.xml b/features/login/impl/src/main/res/values-uk/translations.xml
index b93a66fed2..0d8eca42cf 100644
--- a/features/login/impl/src/main/res/values-uk/translations.xml
+++ b/features/login/impl/src/main/res/values-uk/translations.xml
@@ -32,7 +32,7 @@
"Неправильне ім\'я користувача та/або пароль"
"Це недійсний ідентифікатор користувача. Очікуваний формат: \'@user:homeserver.org\'"
"Цей сервер налаштований на використання оновлюваних токенів. Вони не підтримуються, якщо використовується вхід за допомогою основі пароля."
- "Обраний домашній сервер не підтримує вхід за допомогою пароля або OIDC. Зверніться до адміністратора або виберіть інший домашній сервер."
+ "Обраний домашній сервер не підтримує вхід за допомогою пароля або OAuth. Зверніться до адміністратора або виберіть інший домашній сервер."
"Введіть свої дані"
"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."
"З поверненням!"
diff --git a/features/login/impl/src/main/res/values-ur/translations.xml b/features/login/impl/src/main/res/values-ur/translations.xml
index 334d5b6ea6..6762ab16af 100644
--- a/features/login/impl/src/main/res/values-ur/translations.xml
+++ b/features/login/impl/src/main/res/values-ur/translations.xml
@@ -24,7 +24,7 @@
"غلط صارف نام اور/یا لفظ عبور"
"یہ صالح صارف شناسه نہیں ہے۔ متوقع شکل: @صارف:منزلی خادم"
"یہ خادم تازگی کی رموزِ ممیز استعمال کرنے کے لئے تشکیل دیا گیا ہے۔ لفظ عبور پر مبنی دخول استعمال کرتے ہوئے ان کی حمایت نہیں کی جاتی۔"
- "منتخب منزلی خادم کلمۂ عبوری یا OIDC دخول کا تعاون نہیں کرتا۔ برائے مہربانی اپنے منتظم سے رابطہ کریں یا کوئی اور منزلی خادم چنیں۔"
+ "منتخب منزلی خادم کلمۂ عبوری یا OAuth دخول کا تعاون نہیں کرتا۔ برائے مہربانی اپنے منتظم سے رابطہ کریں یا کوئی اور منزلی خادم چنیں۔"
"اپنی تفصیلات درج کریں"
"میٹرکس محفوظ، غیر مرکزی مواصلت کے لئے ایک کھلا شبکہ ہے۔"
"واپس خوش آمدید!"
diff --git a/features/login/impl/src/main/res/values-uz/translations.xml b/features/login/impl/src/main/res/values-uz/translations.xml
index 07b3d8835b..546397935b 100644
--- a/features/login/impl/src/main/res/values-uz/translations.xml
+++ b/features/login/impl/src/main/res/values-uz/translations.xml
@@ -31,7 +31,7 @@
"Notog\'ri foydalanuvchi nomi va/yoki parol"
"Bu haqiqiy foydalanuvchi identifikatori emas. Kutilayotgan format: \'@user:homeserver.org\'"
"Ushbu server yangilash tokenlaridan foydalanishga moslashtirilgan. Parolga asoslangan tizimga kirishda bunday tokenlar qoʻllab-quvvatlanmaydi."
- "Tanlangan uy serveri parol yoki OIDC loginni qo\'lab-quvvatlamaydi. Iltimos, administratoringizga murojaat qiling yoki boshqa uy serverini tanlang."
+ "Tanlangan uy serveri parol yoki OAuth loginni qo\'lab-quvvatlamaydi. Iltimos, administratoringizga murojaat qiling yoki boshqa uy serverini tanlang."
"Tafsilotlaringizni kiriting"
"Matrix xavfsiz, markazlashmagan aloqa uchun ochiq tarmoqdir."
"Qaytib kelganingizdan xursandmiz!"
diff --git a/features/login/impl/src/main/res/values-vi/translations.xml b/features/login/impl/src/main/res/values-vi/translations.xml
index 4d9b921ea5..66089a4708 100644
--- a/features/login/impl/src/main/res/values-vi/translations.xml
+++ b/features/login/impl/src/main/res/values-vi/translations.xml
@@ -31,7 +31,7 @@
"Tên người dùng và/hoặc mật khẩu không chính xác"
"Đây không phải là mã nhận dạng người dùng hợp lệ. Định dạng mong đợi: ‘@user:homeserver.org’"
"Máy chủ này được cấu hình sử dụng refresh token. Điều này không được hỗ trợ khi đăng nhập bằng mật khẩu."
- "Homeserver đã chọn không hỗ trợ đăng nhập bằng mật khẩu hoặc OIDC. Vui lòng liên hệ với quản trị viên của cậu hoặc chọn một homeserver khác."
+ "Homeserver đã chọn không hỗ trợ đăng nhập bằng mật khẩu hoặc OAuth. Vui lòng liên hệ với quản trị viên của cậu hoặc chọn một homeserver khác."
"Nhập thông tin chi tiết của bạn."
"Matrix là một mạng mở cho việc liên lạc an toàn và phi tập trung."
"Chào mừng bạn quay trở lại!"
diff --git a/features/login/impl/src/main/res/values-zh-rTW/translations.xml b/features/login/impl/src/main/res/values-zh-rTW/translations.xml
index 7170288c5d..c4d16f5928 100644
--- a/features/login/impl/src/main/res/values-zh-rTW/translations.xml
+++ b/features/login/impl/src/main/res/values-zh-rTW/translations.xml
@@ -32,7 +32,7 @@
"不正確的使用者名稱或密碼"
"此非有效的使用者識別字串。預期的格式:‘@user:homeserver.org’"
"此伺服器已設定為使用重新整理權杖。使用以密碼為基礎的登入方式時,不支援這些功能。"
- "選定的家伺服器不支援密碼或 OIDC 登入。請聯絡您的管理員或選擇其他家伺服器。"
+ "選定的家伺服器不支援密碼或 OAuth 登入。請聯絡您的管理員或選擇其他家伺服器。"
"輸入您的詳細資料"
"Matrix 是一個開放網路,為了安全且去中心化的通訊而生。"
"歡迎回來!"
diff --git a/features/login/impl/src/main/res/values-zh/translations.xml b/features/login/impl/src/main/res/values-zh/translations.xml
index f9c9141ff2..8e86b51d7b 100644
--- a/features/login/impl/src/main/res/values-zh/translations.xml
+++ b/features/login/impl/src/main/res/values-zh/translations.xml
@@ -1,104 +1,109 @@
"更改账户提供方"
- "服务器地址"
+ "主服务器地址"
"输入搜索词或域名地址。"
"搜索公司、社区或私人服务器。"
"寻找账户提供方"
- "这是您的对话将存在的地方,就像您使用电子邮件提供方来保存电子邮件一样。"
- "您即将登录 %s"
- "这是您的对话将存在的地方,就像您使用电子邮件提供方来保存电子邮件一样。"
- "您即将在 %s 上创建一个帐户"
+ "这是你的对话将存在的地方,就像你使用邮件提供者来存储电子邮件那样。"
+ "你即将登录到 %s"
+ "这是你的对话将存在的地方,就像你使用邮件提供者来存储电子邮件那样。"
+ "你即将在 %s 上创建账户"
"Matrix.org 由 Matrix.org 基金会运营,是用于安全、去中心化的通信的公共 Matrix 网络上的大型免费服务器。"
- "其他"
- "使用其他账户提供商,例如您自己的私人服务器或工作账户。"
+ "其它"
+ "使用其它账户提供者,例如你自己的私有服务器或工作账户。"
"更改账户提供方"
"Google Play"
- "%1$s 需要 Element Pro 应用。请从应用商店下载。"
- "需要 Element Pro 版"
- "我们无法访问此服务器。请检查您输入的服务器网址是否正确。如果 URL 正确,请联系您的服务器管理员寻求进一步帮助。"
- "由于 .well-known 文件中存在问题,服务器不可用:
+ "%1$s 要求 Element Pro。请从应用商店下载。"
+ "需要 Element Pro"
+ "我们无法访问此服务器。请检查输入的服务器 URL 是否正确。如果 URL 正确,请联系服务器管理员寻求进一步帮助。"
+ "由于 .well-known 文件存在问题,服务器不可用:
%1$s"
- "所选账户提供商不支持跨屏同步。需要升级服务器才能使用%1$s。"
- "%1$s不允许连接到%2$s。"
- "本应用已配置为允许访问:%1$s 。"
- "账户提供商%1$s 不被允许。"
- "服务器网址"
+ "所选账户提供者不支持滑动同步。需要升级服务器才能使用 %1$s。"
+ "%1$s 不允许连接到 %2$s。"
+ "此 app 已配置为允许访问:%1$s。"
+ "账户提供者 %1$s 不被允许。"
+ "主服务器 URL"
"输入域名地址。"
- "您的服务器地址是什么?"
+ "你的服务器地址是什么?"
"选择服务器"
"创建账户"
- "该账户已被停用。"
- "错误的用户名和/或密码"
- "这不是合法的用户 ID。期望格式:‘@user:homeserver.org’。"
+ "此账户已被删除。"
+ "用户名与(或)密码不正确"
+ "这不是合法的用户 ID。预期格式:“@user:homeserver.org”。"
"此服务器使用刷新令牌。使用密码登录时不支持这些功能。"
- "该服务器不支持密码登录和 OIDC 第三方账户登录。请联系服务器管理员,或选择别的服务器。"
- "输入您的详细信息"
+ "该服务器不支持密码登录与 OAuth 登录。请联系服务器管理员或选择另一服务器。"
+ "输入详细信息"
"Matrix 是一个用于安全、去中心化通信的开放网络。"
"欢迎回来!"
"登录到 %1$s"
"打开 Element Classic"
"在你的设备上打开 Element Classic"
"前往“设置” > “安全与隐私”"
+ "在加密密钥管理中选择“恢复加密消息”。"
+ "按指示启用密钥存储"
+ "返回到 %1$s"
+ "请先启用密钥存储再继续处理 %1$s"
"版本%1$s"
+ "正在检查账户"
"手动登录"
"登录到 %1$s"
"使用二维码登录"
"创建账户"
"欢迎回来"
- "欢迎使用 %1$s,快而简约的消息应用。"
+ "欢迎使用迄今最快的 %1$s,速度与简洁的极致。"
"欢迎使用 %1$s,速度与简洁的极致。"
- "融入您的 Element"
+ "融入 Element"
"建立安全连接"
- "无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。"
+ "无法与新设备建立安全连接。你的现有设备仍然安全,无需担心。"
"现在怎么办?"
"如果这是网络问题,请尝试使用二维码再次登录"
"如果遇到同样的问题,请尝试使用不同的 WiFi 网络或使用移动数据代替 WiFi"
"如果不起作用,请手动登录"
"连接不安全"
- "您会被要求输入此设备上显示的两位数。"
- "在您的其他设备上输入下面的数字"
- "在其他设备登录后重试,或使用另一个已登录的设备。"
- "其他设备未登录"
+ "你将被要求输入此设备上显示的两位数字。"
+ "在你的其它设备上输入以下数字"
+ "在其它设备登录后重试,或使用另一个已登录的设备。"
+ "尚未登录的其它设备"
"登录被另一台设备取消"
"登录请求已取消"
- "其它设备未接受请求"
+ "另一设备上的登录请求已被拒绝。"
"登录被拒绝"
- "您无需额外操作。"
- "您已在另一台设备登录。"
+ "无需额外操作。"
+ "你已在另一设备上登录。"
"登录已过期. 请重试."
"登录未及时完成"
"另一个设备不支持使用二维码登录 %s.
尝试手动或使用另一个设备扫描二维码."
- "不支持二维码"
+ "二维码不受支持"
"账户提供方不支持 %1$s."
"不支持 %1$s."
"准备进行扫描"
"在桌面设备上打开 %1$s"
"点击你的头像"
"选择 %1$s"
- "「连接新设备」"
+ "“关联新设备”"
"使用此设备扫描二维码"
- "仅在您的账户提供方支持时才可用。"
+ "仅在账户提供者支持时可用。"
"在另一台设备上打开 %1$s 以获取二维码"
- "使用其他设备上显示的二维码。"
- "再试一次"
+ "使用其它设备上显示的二维码。"
+ "重试"
"二维码错误"
"转到摄像头设置"
- "您需要授予 %1$s 使用设备摄像头的权限才能继续。"
- "允许摄像头权限以扫描 QR 码"
+ "你需要授予 %1$s 使用设备摄像头的权限才能继续。"
+ "允许访问摄像头以扫描二维码"
"扫描二维码"
"重新开始"
"发生了意外错误。请再试一次。"
- "等着您的其他设备"
- "您的账户提供方可能会要求您提供以下代码来验证登录。"
- "您的验证码"
+ "正在等待其它设备"
+ "你的账户提供者可能会要求你提供以下代码以验证登录。"
+ "你的验证码"
"更改账户提供方"
"专为 Element 员工提供的私人服务器。"
"Matrix 是一个用于安全、去中心化通信的开放网络。"
- "这是您的对话将存在的地方,就像您使用电子邮件提供方来保存电子邮件一样。"
+ "这是你的对话将存在的地方,就像你使用邮件提供者来存储电子邮件那样。"
"即将登录 %1$s"
- "选择账户提供商"
+ "选择账户提供者"
"即将在 %1$s 上创建一个账户"
diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml
index f8d5ffa651..10fb6ef04a 100644
--- a/features/login/impl/src/main/res/values/localazy.xml
+++ b/features/login/impl/src/main/res/values/localazy.xml
@@ -32,7 +32,7 @@
"Incorrect username and/or password"
"This is not a valid user identifier. Expected format: ‘@user:homeserver.org’"
"This server is configured to use refresh tokens. These aren\'t supported when using password based login."
- "The selected homeserver doesn\'t support password or OIDC login. Please contact your admin or choose another homeserver."
+ "The selected homeserver doesn\'t support password or OAuth login. Please contact your admin or choose another homeserver."
"Enter your details"
"Matrix is an open network for secure, decentralised communication."
"Welcome back!"
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPointTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPointTest.kt
index 86a629270f..a05194d008 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPointTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPointTest.kt
@@ -17,7 +17,7 @@ import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.classic.FakeElementClassicConnection
import io.element.android.features.preferences.test.FakePreferencesEntryPoint
-import io.element.android.libraries.oidc.test.customtab.FakeOidcActionFlow
+import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode
import kotlinx.coroutines.test.runTest
@@ -39,7 +39,7 @@ class DefaultLoginEntryPointTest {
buildContext = buildContext,
plugins = plugins,
accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
- oidcActionFlow = FakeOidcActionFlow(),
+ oAuthActionFlow = FakeOAuthActionFlow(),
appCoroutineScope = backgroundScope,
elementClassicConnection = FakeElementClassicConnection(),
preferencesEntryPoint = FakePreferencesEntryPoint(),
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt
index 1fb5d37627..274b58ee49 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt
@@ -50,7 +50,7 @@ class ChangeServerPresenterTest {
fun `present - change server ok`() = runTest {
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
createPresenter(
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt
index 9d2628005c..112d8d7108 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt
@@ -79,7 +79,7 @@ class QrCodeLoginFlowNodeTest {
qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.ConnectionInsecure)
assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.InsecureChannelDetected))
- qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.OidcMetadataInvalid)
+ qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.OAuthMetadataInvalid)
assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.UnknownError))
qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Unknown)
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt
index f7ff5d384d..61ec7cc698 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt
@@ -6,17 +6,20 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.login.impl.screens.chooseaccountprovider
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.login.impl.accountprovider.anAccountProvider
import io.element.android.libraries.architecture.AsyncData
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
@@ -25,36 +28,31 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class ChooseAccountProviderViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back invokes the expected callback`() {
+ fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
ensureCalledOnce {
- rule.setChooseAccountProviderView(
+ setChooseAccountProviderView(
state = aChooseAccountProviderState(
eventSink = eventSink,
),
onBackClick = it,
)
- rule.pressBack()
+ pressBack()
}
}
@Config(qualifiers = "h1024dp")
@Test
- fun `selecting an account provider emits the the expected event`() {
+ fun `selecting an account provider emits the the expected event`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder()
- rule.setChooseAccountProviderView(
+ setChooseAccountProviderView(
state = aChooseAccountProviderState(
accountProviders = listOf(
ChooseAccountProviderPresenterTest.accountProvider1,
@@ -64,27 +62,27 @@ class ChooseAccountProviderViewTest {
eventSink = eventSink,
),
)
- rule.onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick()
+ onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick()
eventSink.assertSingle(ChooseAccountProviderEvents.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1))
}
@Test
- fun `when error is displayed - closing the dialog emits the expected event`() {
+ fun `when error is displayed - closing the dialog emits the expected event`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder()
- rule.setChooseAccountProviderView(
+ setChooseAccountProviderView(
state = aChooseAccountProviderState(
loginMode = AsyncData.Failure(AN_EXCEPTION),
eventSink = eventSink,
),
)
- rule.clickOn(CommonStrings.action_ok)
+ clickOn(CommonStrings.action_ok)
eventSink.assertSingle(ChooseAccountProviderEvents.ClearError)
}
- private fun AndroidComposeTestRule.setChooseAccountProviderView(
+ private fun AndroidComposeUiTest.setChooseAccountProviderView(
state: ChooseAccountProviderState,
onBackClick: () -> Unit = EnsureNeverCalled(),
- onOidcDetails: (OidcDetails) -> Unit = EnsureNeverCalledWithParam(),
+ onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(),
onNeedLoginPassword: () -> Unit = EnsureNeverCalled(),
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
onCreateAccountContinue: (url: String) -> Unit = EnsureNeverCalledWithParam(),
@@ -93,7 +91,7 @@ class ChooseAccountProviderViewTest {
ChooseAccountProviderView(
state = state,
onBackClick = onBackClick,
- onOidcDetails = onOidcDetails,
+ onOAuthDetails = onOAuthDetails,
onNeedLoginPassword = onNeedLoginPassword,
onLearnMoreClick = onLearnMoreClick,
onCreateAccountContinue = onCreateAccountContinue,
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt
index 6372841250..a9045ab152 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt
@@ -22,9 +22,9 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
-import io.element.android.libraries.oidc.api.OidcAction
-import io.element.android.libraries.oidc.api.OidcActionFlow
-import io.element.android.libraries.oidc.test.customtab.FakeOidcActionFlow
+import io.element.android.libraries.oauth.api.OAuthAction
+import io.element.android.libraries.oauth.api.OAuthActionFlow
+import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest
@@ -74,7 +74,7 @@ class ConfirmAccountProviderPresenterTest {
fun `present - continue oidc`() = runTest {
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
val presenter = createConfirmAccountProviderPresenter(
@@ -89,21 +89,21 @@ class ConfirmAccountProviderPresenterTest {
val successState = awaitItem()
assertThat(successState.submitEnabled).isFalse()
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
- assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
+ assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
}
}
@Test
- fun `present - oidc - cancel with failure`() = runTest {
+ fun `present - OAuth - cancel with failure`() = runTest {
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
- val defaultOidcActionFlow = FakeOidcActionFlow()
+ val defaultOAuthActionFlow = FakeOAuthActionFlow()
val presenter = createConfirmAccountProviderPresenter(
matrixAuthenticationService = authenticationService,
- defaultOidcActionFlow = defaultOidcActionFlow,
+ defaultOAuthActionFlow = defaultOAuthActionFlow,
)
presenter.test {
val initialState = awaitItem()
@@ -114,25 +114,25 @@ class ConfirmAccountProviderPresenterTest {
val successState = awaitItem()
assertThat(successState.submitEnabled).isFalse()
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
- assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
- authenticationService.givenOidcCancelError(AN_EXCEPTION)
- defaultOidcActionFlow.post(OidcAction.GoBack())
+ assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
+ authenticationService.givenOAuthCancelError(AN_EXCEPTION)
+ defaultOAuthActionFlow.post(OAuthAction.GoBack())
val cancelFailureState = awaitItem()
assertThat(cancelFailureState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
}
}
@Test
- fun `present - oidc - cancel with success`() = runTest {
+ fun `present - OAuth - cancel with success`() = runTest {
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
- val defaultOidcActionFlow = FakeOidcActionFlow()
+ val defaultOAuthActionFlow = FakeOAuthActionFlow()
val presenter = createConfirmAccountProviderPresenter(
matrixAuthenticationService = authenticationService,
- defaultOidcActionFlow = defaultOidcActionFlow,
+ defaultOAuthActionFlow = defaultOAuthActionFlow,
)
presenter.test {
val initialState = awaitItem()
@@ -143,24 +143,24 @@ class ConfirmAccountProviderPresenterTest {
val successState = awaitItem()
assertThat(successState.submitEnabled).isFalse()
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
- assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
- defaultOidcActionFlow.post(OidcAction.GoBack())
+ assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
+ defaultOAuthActionFlow.post(OAuthAction.GoBack())
val cancelFinalState = awaitItem()
assertThat(cancelFinalState.loginMode).isInstanceOf(AsyncData.Uninitialized::class.java)
}
}
@Test
- fun `present - oidc - cancel to unblock`() = runTest {
+ fun `present - OAuth - cancel to unblock`() = runTest {
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
- val defaultOidcActionFlow = FakeOidcActionFlow()
+ val defaultOAuthActionFlow = FakeOAuthActionFlow()
val presenter = createConfirmAccountProviderPresenter(
matrixAuthenticationService = authenticationService,
- defaultOidcActionFlow = defaultOidcActionFlow,
+ defaultOAuthActionFlow = defaultOAuthActionFlow,
)
presenter.test {
val initialState = awaitItem()
@@ -168,23 +168,23 @@ class ConfirmAccountProviderPresenterTest {
val loadingState = awaitItem()
assertThat(loadingState.submitEnabled).isTrue()
assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
- defaultOidcActionFlow.post(OidcAction.GoBack(toUnblock = true))
+ defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true))
val cancelFinalState = awaitItem()
assertThat(cancelFinalState.loginMode).isInstanceOf(AsyncData.Uninitialized::class.java)
}
}
@Test
- fun `present - oidc - success with failure`() = runTest {
+ fun `present - OAuth - success with failure`() = runTest {
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
- val defaultOidcActionFlow = FakeOidcActionFlow()
+ val defaultOAuthActionFlow = FakeOAuthActionFlow()
val presenter = createConfirmAccountProviderPresenter(
matrixAuthenticationService = authenticationService,
- defaultOidcActionFlow = defaultOidcActionFlow,
+ defaultOAuthActionFlow = defaultOAuthActionFlow,
)
presenter.test {
val initialState = awaitItem()
@@ -195,9 +195,9 @@ class ConfirmAccountProviderPresenterTest {
val successState = awaitItem()
assertThat(successState.submitEnabled).isFalse()
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
- assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
+ assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
authenticationService.givenLoginError(AN_EXCEPTION)
- defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
+ defaultOAuthActionFlow.post(OAuthAction.Success("aUrl"))
val cancelLoadingState = awaitItem()
assertThat(cancelLoadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
val cancelFailureState = awaitItem()
@@ -206,16 +206,16 @@ class ConfirmAccountProviderPresenterTest {
}
@Test
- fun `present - oidc - success with success`() = runTest {
+ fun `present - OAuth - success with success`() = runTest {
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
- val defaultOidcActionFlow = FakeOidcActionFlow()
+ val defaultOidcActionFlow = FakeOAuthActionFlow()
val presenter = createConfirmAccountProviderPresenter(
matrixAuthenticationService = authenticationService,
- defaultOidcActionFlow = defaultOidcActionFlow,
+ defaultOAuthActionFlow = defaultOidcActionFlow,
)
presenter.test {
val initialState = awaitItem()
@@ -226,8 +226,8 @@ class ConfirmAccountProviderPresenterTest {
val successState = awaitItem()
assertThat(successState.submitEnabled).isFalse()
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
- assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
- defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
+ assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
+ defaultOidcActionFlow.post(OAuthAction.Success("aUrl"))
val successSuccessState = awaitItem()
assertThat(successSuccessState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
}
@@ -311,10 +311,10 @@ class ConfirmAccountProviderPresenterTest {
}
@Test
- fun `present - confirm account creation with oidc is successful`() = runTest {
+ fun `present - confirm account creation with OAuth is successful`() = runTest {
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
val presenter = createConfirmAccountProviderPresenter(
@@ -327,16 +327,16 @@ class ConfirmAccountProviderPresenterTest {
skipItems(1) // Loading
val submittedState = awaitItem()
assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Success::class.java)
- assertThat(submittedState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
+ assertThat(submittedState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
}
}
@Test
- fun `present - confirm account creation with oidc and url continues with oidc`() = runTest {
+ fun `present - confirm account creation with OAuth and url continues with OAuth`() = runTest {
val aUrl = "aUrl"
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
- Result.success(aMatrixHomeServerDetails(supportsOidcLogin = true))
+ Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
},
)
val presenter = createConfirmAccountProviderPresenter(
@@ -350,12 +350,12 @@ class ConfirmAccountProviderPresenterTest {
skipItems(1) // Loading
val submittedState = awaitItem()
assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Success::class.java)
- assertThat(submittedState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
+ assertThat(submittedState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
}
}
@Test
- fun `present - confirm account creation without oidc and with url continuing with url`() = runTest {
+ fun `present - confirm account creation without OAuth and with url continuing with url`() = runTest {
val aUrl = "aUrl"
val authenticationService = FakeMatrixAuthenticationService(
setHomeserverResult = {
@@ -380,14 +380,14 @@ class ConfirmAccountProviderPresenterTest {
params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
- defaultOidcActionFlow: OidcActionFlow = FakeOidcActionFlow(),
+ defaultOAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
) = ConfirmAccountProviderPresenter(
params = params,
accountProviderDataSource = accountProviderDataSource,
loginHelper = createLoginHelper(
authenticationService = matrixAuthenticationService,
- oidcActionFlow = defaultOidcActionFlow,
+ oAuthActionFlow = defaultOAuthActionFlow,
webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
),
)
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt
index 26da50da63..c0e7e5c378 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt
@@ -6,20 +6,23 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.login.impl.screens.loginpassword
import androidx.activity.ComponentActivity
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.matrix.test.A_PASSWORD
import io.element.android.libraries.matrix.test.A_USER_NAME
@@ -30,158 +33,154 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class LoginPasswordViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back invoke back callback`() {
+ fun `clicking on back invoke back callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
eventSink = eventsRecorder
),
onBackClick = callback,
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `changing login invokes the expected event`() {
+ fun `changing login invokes the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
eventSink = eventsRecorder,
),
)
- val userNameHint = rule.activity.getString(CommonStrings.common_username)
- rule.onNodeWithText(userNameHint).performTextInput(A_USER_NAME)
+ val userNameHint = activity!!.getString(CommonStrings.common_username)
+ onNodeWithText(userNameHint).performTextInput(A_USER_NAME)
eventsRecorder.assertSingle(
LoginPasswordEvents.SetLogin(A_USER_NAME)
)
}
@Test
- fun `changing login removes new lines the expected event`() {
+ fun `changing login removes new lines the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
eventSink = eventsRecorder,
),
)
- val userNameHint = rule.activity.getString(CommonStrings.common_username)
- rule.onNodeWithText(userNameHint).performTextInput("a\nb")
+ val userNameHint = activity!!.getString(CommonStrings.common_username)
+ onNodeWithText(userNameHint).performTextInput("a\nb")
eventsRecorder.assertSingle(
LoginPasswordEvents.SetLogin("ab")
)
}
@Test
- fun `clearing login invokes the expected event`() {
+ fun `clearing login invokes the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
formState = aLoginFormState(A_USER_NAME),
eventSink = eventsRecorder,
),
)
- val a11yClear = rule.activity.getString(CommonStrings.action_clear)
- rule.onNodeWithContentDescription(a11yClear).performClick()
+ val a11yClear = activity!!.getString(CommonStrings.action_clear)
+ onNodeWithContentDescription(a11yClear).performClick()
eventsRecorder.assertSingle(
LoginPasswordEvents.SetLogin("")
)
}
@Test
- fun `changing password invokes the expected event`() {
+ fun `changing password invokes the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
eventSink = eventsRecorder,
),
)
- val userNameHint = rule.activity.getString(CommonStrings.common_password)
- rule.onNodeWithText(userNameHint).performTextInput(A_PASSWORD)
+ val userNameHint = activity!!.getString(CommonStrings.common_password)
+ onNodeWithText(userNameHint).performTextInput(A_PASSWORD)
eventsRecorder.assertSingle(
LoginPasswordEvents.SetPassword(A_PASSWORD)
)
}
@Test
- fun `reveal password makes the password visible`() {
+ fun `reveal password makes the password visible`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
formState = aLoginFormState(password = A_PASSWORD),
eventSink = eventsRecorder,
),
)
- rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••"))
+ onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••"))
+ val resources = activity!!.resources
// Show password
- val a11yShowPassword = rule.activity.getString(CommonStrings.a11y_show_password)
- rule.onNodeWithContentDescription(a11yShowPassword).performClick()
- rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText(A_PASSWORD))
+ val a11yShowPassword = resources.getString(CommonStrings.a11y_show_password)
+ onNodeWithContentDescription(a11yShowPassword).performClick()
+ onNodeWithTag(TestTags.loginPassword.value).assert(hasText(A_PASSWORD))
// Hide password
- val a11yHidePassword = rule.activity.getString(CommonStrings.a11y_hide_password)
- rule.onNodeWithContentDescription(a11yHidePassword).performClick()
- rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••"))
+ val a11yHidePassword = resources.getString(CommonStrings.a11y_hide_password)
+ onNodeWithContentDescription(a11yHidePassword).performClick()
+ onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••"))
}
@Test
- fun `when login is empty, continue button is not enabled`() {
+ fun `when login is empty, continue button is not enabled`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
formState = aLoginFormState(password = A_PASSWORD),
eventSink = eventsRecorder,
),
)
- val continueStr = rule.activity.getString(CommonStrings.action_continue)
- rule.onNodeWithText(continueStr).assertIsNotEnabled()
+ val continueStr = activity!!.getString(CommonStrings.action_continue)
+ onNodeWithText(continueStr).assertIsNotEnabled()
}
@Test
- fun `when password is empty, continue button is not enabled`() {
+ fun `when password is empty, continue button is not enabled`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
formState = aLoginFormState(login = A_USER_NAME),
eventSink = eventsRecorder,
),
)
- val continueStr = rule.activity.getString(CommonStrings.action_continue)
- rule.onNodeWithText(continueStr).assertIsNotEnabled()
+ val continueStr = activity!!.getString(CommonStrings.action_continue)
+ onNodeWithText(continueStr).assertIsNotEnabled()
}
@Config(qualifiers = "h1024dp")
@Test
- fun `clicking on Continue sends expected event`() {
+ fun `clicking on Continue sends expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLoginPasswordView(
+ setLoginPasswordView(
aLoginPasswordState(
formState = aLoginFormState(login = A_USER_NAME, password = A_PASSWORD),
eventSink = eventsRecorder,
),
)
- val continueStr = rule.activity.getString(CommonStrings.action_continue)
- rule.onNodeWithText(continueStr).assertIsEnabled()
- rule.clickOn(CommonStrings.action_continue)
+ val continueStr = activity!!.getString(CommonStrings.action_continue)
+ onNodeWithText(continueStr).assertIsEnabled()
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(
LoginPasswordEvents.Submit
)
}
}
-private fun AndroidComposeTestRule.setLoginPasswordView(
+private fun AndroidComposeUiTest.setLoginPasswordView(
state: LoginPasswordState,
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt
index 1fdfb7e070..8249694278 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt
@@ -31,8 +31,8 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
import io.element.android.libraries.matrix.test.A_LOGIN_HINT
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.libraries.matrix.test.core.aBuildMeta
-import io.element.android.libraries.oidc.api.OidcActionFlow
-import io.element.android.libraries.oidc.test.customtab.FakeOidcActionFlow
+import io.element.android.libraries.oauth.api.OAuthActionFlow
+import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
import io.element.android.libraries.sessionstorage.test.aSessionData
@@ -312,11 +312,11 @@ private fun createPresenter(
)
fun createLoginHelper(
- oidcActionFlow: OidcActionFlow = FakeOidcActionFlow(),
+ oAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
): LoginHelper = LoginHelper(
- oidcActionFlow = oidcActionFlow,
+ oAuthActionFlow = oAuthActionFlow,
authenticationService = authenticationService,
webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
)
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt
index ad09445075..bcb62ea707 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt
@@ -6,20 +6,23 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.login.impl.screens.onboarding
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import com.google.testing.junit.testparameterinjector.KotlinTestParameters.namedTestValues
import com.google.testing.junit.testparameterinjector.TestParameter
import io.element.android.features.login.impl.R
import io.element.android.features.login.impl.login.LoginMode
import io.element.android.libraries.architecture.AsyncData
-import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.api.auth.OAuthDetails
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
@@ -29,22 +32,17 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBack
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestParameterInjector
@RunWith(RobolectricTestParameterInjector::class)
class OnboardingViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `when can create account - clicking on create account calls the expected callback`() {
+ fun `when can create account - clicking on create account calls the expected callback`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
canCreateAccount = true,
showDeveloperSettings = false,
@@ -52,40 +50,40 @@ class OnboardingViewTest {
),
onCreateAccount = callback,
)
- rule.clickOn(R.string.screen_onboarding_sign_up)
+ clickOn(R.string.screen_onboarding_sign_up)
// Developer settings should not be shown
- val developerSettingsText = rule.activity.getString(CommonStrings.common_developer_options)
- rule.onNodeWithContentDescription(developerSettingsText).assertDoesNotExist()
+ val developerSettingsText = activity!!.getString(CommonStrings.common_developer_options)
+ onNodeWithContentDescription(developerSettingsText).assertDoesNotExist()
}
}
@Test
- fun `when can go back - clicking on back calls the expected callback`() {
+ fun `when can go back - clicking on back calls the expected callback`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
isAddingAccount = true,
eventSink = eventSink,
),
onBackClick = callback,
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() {
+ fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
canLoginWithQrCode = true,
eventSink = eventSink,
),
onSignInWithQrCode = callback,
)
- rule.clickOn(R.string.screen_onboarding_sign_in_with_qr_code)
+ clickOn(R.string.screen_onboarding_sign_in_with_qr_code)
}
}
@@ -95,10 +93,10 @@ class OnboardingViewTest {
"can search account provider" to false,
"cannot search account provider" to true,
)
- ) {
+ ) = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
ensureCalledOnceWithParam(mustChooseAccountProvider) { callback ->
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
canLoginWithQrCode = true,
mustChooseAccountProvider = mustChooseAccountProvider,
@@ -106,7 +104,7 @@ class OnboardingViewTest {
),
onSignIn = callback,
)
- rule.clickOn(R.string.screen_onboarding_sign_in_manually)
+ clickOn(R.string.screen_onboarding_sign_in_manually)
}
}
@@ -116,10 +114,10 @@ class OnboardingViewTest {
"can search account provider" to false,
"cannot search account provider" to true,
)
- ) {
+ ) = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
ensureCalledOnceWithParam(mustChooseAccountProvider) { callback ->
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
canLoginWithQrCode = false,
canCreateAccount = false,
@@ -128,89 +126,89 @@ class OnboardingViewTest {
),
onSignIn = callback,
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
}
}
@Test
- fun `when sign in to pre defined account provider - clicking on button emits the expected event`() {
+ fun `when sign in to pre defined account provider - clicking on button emits the expected event`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder()
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
defaultAccountProvider = "element.io",
eventSink = eventSink,
),
)
- val buttonText = rule.activity.getString(R.string.screen_onboarding_sign_in_to, "element.io")
- rule.onNodeWithText(buttonText).performClick()
+ val buttonText = activity!!.getString(R.string.screen_onboarding_sign_in_to, "element.io")
+ onNodeWithText(buttonText).performClick()
eventSink.assertSingle(OnBoardingEvents.OnSignIn("element.io"))
}
@Test
- fun `when error is displayed - closing the dialog emits the expected event`() {
+ fun `when error is displayed - closing the dialog emits the expected event`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder()
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
defaultAccountProvider = "element.io",
loginMode = AsyncData.Failure(AN_EXCEPTION),
eventSink = eventSink,
),
)
- rule.clickOn(CommonStrings.action_ok)
+ clickOn(CommonStrings.action_ok)
eventSink.assertSingle(OnBoardingEvents.ClearError)
}
@Test
- fun `clicking on report a problem calls the sign in callback`() {
+ fun `clicking on report a problem calls the sign in callback`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
canReportBug = true,
eventSink = eventSink,
),
onReportProblem = callback,
)
- val text = rule.activity.getString(CommonStrings.common_report_a_problem)
- rule.onNodeWithText(text).assertExists()
- rule.clickOn(CommonStrings.common_report_a_problem)
+ val text = activity!!.getString(CommonStrings.common_report_a_problem)
+ onNodeWithText(text).assertExists()
+ clickOn(CommonStrings.common_report_a_problem)
}
}
@Test
- fun `clicking on settings calls the developer settings callback`() {
+ fun `clicking on settings calls the developer settings callback`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
showDeveloperSettings = true,
eventSink = eventSink,
),
onDeveloperSettingsClick = callback,
)
- val text = rule.activity.getString(CommonStrings.common_developer_options)
- rule.onNodeWithContentDescription(text).performClick()
+ val text = activity!!.getString(CommonStrings.common_developer_options)
+ onNodeWithContentDescription(text).performClick()
}
}
@Test
- fun `cannot report a problem when the feature is disabled`() {
+ fun `cannot report a problem when the feature is disabled`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder(expectEvents = false)
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
canReportBug = false,
eventSink = eventSink,
),
)
- val text = rule.activity.getString(CommonStrings.common_report_a_problem)
- rule.onNodeWithText(text).assertDoesNotExist()
+ val text = activity!!.getString(CommonStrings.common_report_a_problem)
+ onNodeWithText(text).assertDoesNotExist()
}
@Test
- fun `when success PasswordLogin - the expected callback is invoked and the event is received`() {
+ fun `when success PasswordLogin - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder()
ensureCalledOnce { callback ->
- rule.setOnboardingView(
+ setOnboardingView(
state = anOnBoardingState(
loginMode = AsyncData.Success(LoginMode.PasswordLogin),
eventSink = eventSink,
@@ -222,27 +220,27 @@ class OnboardingViewTest {
}
@Test
- fun `when success Oidc - the expected callback is invoked and the event is received`() {
+ fun `when success Oidc - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder()
- val oidcDetails = OidcDetails("aUrl")
- ensureCalledOnceWithParam(oidcDetails) { callback ->
- rule.setOnboardingView(
+ val oAuthDetails = OAuthDetails("aUrl")
+ ensureCalledOnceWithParam(oAuthDetails) { callback ->
+ setOnboardingView(
state = anOnBoardingState(
- loginMode = AsyncData.Success(LoginMode.Oidc(oidcDetails)),
+ loginMode = AsyncData.Success(LoginMode.OAuth(oAuthDetails)),
eventSink = eventSink,
),
- onOidcDetails = callback,
+ onOAuthDetails = callback,
)
}
eventSink.assertSingle(OnBoardingEvents.ClearError)
}
@Test
- fun `when success AccountCreation - the expected callback is invoked and the event is received`() {
+ fun `when success AccountCreation - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest {
val eventSink = EventsRecorder()
- val oidcDetails = OidcDetails("aUrl")
- ensureCalledOnceWithParam(oidcDetails.url) { callback ->
- rule.setOnboardingView(
+ val oAuthDetails = OAuthDetails("aUrl")
+ ensureCalledOnceWithParam(oAuthDetails.url) { callback ->
+ setOnboardingView(
state = anOnBoardingState(
loginMode = AsyncData.Success(LoginMode.AccountCreation("aUrl")),
eventSink = eventSink,
@@ -253,7 +251,7 @@ class OnboardingViewTest {
eventSink.assertSingle(OnBoardingEvents.ClearError)
}
- private fun AndroidComposeTestRule.setOnboardingView(
+ private fun AndroidComposeUiTest.setOnboardingView(
state: OnBoardingState,
onBackClick: () -> Unit = EnsureNeverCalled(),
onDeveloperSettingsClick: () -> Unit = EnsureNeverCalled(),
@@ -261,7 +259,7 @@ class OnboardingViewTest {
onSignIn: (Boolean) -> Unit = EnsureNeverCalledWithParam(),
onCreateAccount: () -> Unit = EnsureNeverCalled(),
onReportProblem: () -> Unit = EnsureNeverCalled(),
- onOidcDetails: (OidcDetails) -> Unit = EnsureNeverCalledWithParam(),
+ onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(),
onNeedLoginPassword: () -> Unit = EnsureNeverCalled(),
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
onCreateAccountContinue: (url: String) -> Unit = EnsureNeverCalledWithParam(),
@@ -275,7 +273,7 @@ class OnboardingViewTest {
onSignIn = onSignIn,
onCreateAccount = onCreateAccount,
onReportProblem = onReportProblem,
- onOidcDetails = onOidcDetails,
+ onOAuthDetails = onOAuthDetails,
onNeedLoginPassword = onNeedLoginPassword,
onLearnMoreClick = onLearnMoreClick,
onCreateAccountContinue = onCreateAccountContinue,
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt
index a0469a684e..79566625c5 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt
@@ -6,49 +6,47 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.login.impl.screens.qrcode.confirmation
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class QrCodeConfirmationViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the expected callback`() {
+ fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeConfirmationView(
+ setQrCodeConfirmationView(
step = QrCodeConfirmationStep.DisplayCheckCode("12"),
onCancel = callback
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `on Cancel button clicked - calls the expected callback`() {
+ fun `on Cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeConfirmationView(
+ setQrCodeConfirmationView(
step = QrCodeConfirmationStep.DisplayVerificationCode("123456"),
onCancel = callback
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
}
}
- private fun AndroidComposeTestRule.setQrCodeConfirmationView(
+ private fun AndroidComposeUiTest.setQrCodeConfirmationView(
step: QrCodeConfirmationStep,
onCancel: () -> Unit
) {
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt
index de0f689220..2ae68c3485 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.login.impl.screens.qrcode.error
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType
import io.element.android.libraries.ui.strings.CommonStrings
@@ -18,47 +21,42 @@ import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class QrCodeErrorViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the onCancel callback`() {
+ fun `on back pressed - calls the onCancel callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeErrorView(
+ setQrCodeErrorView(
onCancel = callback,
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `on try again button clicked - calls the expected callback`() {
+ fun `on try again button clicked - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeErrorView(
+ setQrCodeErrorView(
onRetry = callback,
)
- rule.clickOn(CommonStrings.action_try_again)
+ clickOn(CommonStrings.action_try_again)
}
}
@Test
- fun `on cancel button clicked - calls the expected callback`() {
+ fun `on cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeErrorView(
+ setQrCodeErrorView(
onCancel = callback,
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
}
}
- private fun AndroidComposeTestRule.setQrCodeErrorView(
+ private fun AndroidComposeUiTest.setQrCodeErrorView(
onRetry: () -> Unit = EnsureNeverCalled(),
onCancel: () -> Unit = EnsureNeverCalled(),
errorScreenType: QrCodeErrorScreenType = QrCodeErrorScreenType.UnknownError,
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt
index cec67e5011..c6812d3759 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.login.impl.screens.qrcode.intro
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.login.impl.R
import io.element.android.tests.testutils.EnsureNeverCalled
@@ -19,42 +22,37 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressBackKey
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class QrCodeIntroViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `on back pressed - calls the expected callback`() {
+ fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeIntroView(
+ setQrCodeIntroView(
state = aQrCodeIntroState(),
onBackClicked = callback
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `on back button clicked - calls the expected callback`() {
+ fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeIntroView(
+ setQrCodeIntroView(
state = aQrCodeIntroState(),
onBackClicked = callback
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `when can continue - calls the expected callback`() {
+ fun `when can continue - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeIntroView(
+ setQrCodeIntroView(
state = aQrCodeIntroState(canContinue = true),
onContinue = callback
)
@@ -62,16 +60,16 @@ class QrCodeIntroViewTest {
}
@Test
- fun `on submit button clicked - emits the Continue event`() {
+ fun `on submit button clicked - emits the Continue event`() = runAndroidComposeUiTest {
val eventRecorder = EventsRecorder()
- rule.setQrCodeIntroView(
+ setQrCodeIntroView(
state = aQrCodeIntroState(eventSink = eventRecorder),
)
- rule.clickOn(R.string.screen_qr_code_login_initial_state_button_title)
+ clickOn(R.string.screen_qr_code_login_initial_state_button_title)
eventRecorder.assertSingle(QrCodeIntroEvents.Continue)
}
- private fun AndroidComposeTestRule.setQrCodeIntroView(
+ private fun AndroidComposeUiTest.setQrCodeIntroView(
state: QrCodeIntroState,
onBackClicked: () -> Unit = EnsureNeverCalled(),
onContinue: () -> Unit = EnsureNeverCalled(),
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt
index b8becd545f..bde960ef1a 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt
@@ -6,12 +6,15 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.login.impl.screens.qrcode.scan
import androidx.activity.ComponentActivity
import androidx.camera.lifecycle.ProcessCameraProvider
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.element.android.libraries.architecture.AsyncAction
@@ -24,16 +27,11 @@ import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBackKey
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class QrCodeScanViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
private var provider: ProcessCameraProvider? = null
@Before
@@ -48,28 +46,28 @@ class QrCodeScanViewTest {
}
@Test
- fun `on back pressed - calls the expected callback`() {
+ fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
- rule.setQrCodeScanView(
+ setQrCodeScanView(
state = aQrCodeScanState(),
onBackClick = callback
)
- rule.pressBackKey()
+ pressBackKey()
}
}
@Test
- fun `on QR code data ready - calls the expected callback`() {
+ fun `on QR code data ready - calls the expected callback`() = runAndroidComposeUiTest {
val data = FakeMatrixQrCodeLoginData()
ensureCalledOnceWithParam(data) { callback ->
- rule.setQrCodeScanView(
+ setQrCodeScanView(
state = aQrCodeScanState(authenticationAction = AsyncAction.Success(data)),
onQrCodeDataReady = callback
)
}
}
- private fun AndroidComposeTestRule.setQrCodeScanView(
+ private fun AndroidComposeUiTest.setQrCodeScanView(
state: QrCodeScanState,
onBackClick: () -> Unit = EnsureNeverCalled(),
onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit = EnsureNeverCalledWithParam(),
diff --git a/features/logout/impl/build.gradle.kts b/features/logout/impl/build.gradle.kts
index 8de7718980..d5356ced63 100644
--- a/features/logout/impl/build.gradle.kts
+++ b/features/logout/impl/build.gradle.kts
@@ -35,6 +35,7 @@ dependencies {
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.dateformatter.api)
+ implementation(projects.libraries.sessionStorage.api)
implementation(projects.libraries.workmanager.api)
api(projects.features.logout.api)
diff --git a/features/logout/impl/src/main/res/values-de/translations.xml b/features/logout/impl/src/main/res/values-de/translations.xml
index 9021aee4da..0218642abc 100644
--- a/features/logout/impl/src/main/res/values-de/translations.xml
+++ b/features/logout/impl/src/main/res/values-de/translations.xml
@@ -4,15 +4,15 @@
"Dieses Gerät entfernen"
"Dieses Gerät entfernen"
"Gerät wird entfernt…"
- "Du bist dabei, dich von deiner letzten Sitzung abzumelden. Wenn dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten."
- "Du hast das Backup deaktiviert"
- "Das Backup deiner Schlüssel lief noch, als du offline gegangen bist. Verbinde dich erneut, damit deine Schlüssel vor dem Abmelden gesichert werden können."
+ "Dies ist dein einziges Gerät. Wenn du es entfernst, benötigst du einen Wiederherstellungsschlüssel, um deine digitale Identität zu bestätigen und deine verschlüsselten Chats bei deiner nächsten Anmeldung wiederherzustellen."
+ "Du bist dabei, den Zugriff auf deine verschlüsselten Chats zu verlieren"
+ "Deine Schlüssel wurden noch gesichert, während du offline gegangen bist. Stelle die Verbindung wieder her, damit deine Schlüssel gesichert werden können, bevor du dieses Gerät entfernst."
"Deine Schlüssel werden noch gesichert"
"Bitte warte, bis der Vorgang abgeschlossen ist, bevor du dieses Gerät entfernst."
"Deine Schlüssel werden noch gesichert"
"Dieses Gerät entfernen"
- "Du bist dabei, dich von deiner letzten Sitzung abzumelden. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten."
+ "Dies ist dein einziges Gerät. Wenn du es entfernst, benötigst du einen Wiederherstellungsschlüssel, um deine digitale Identität zu bestätigen und deine verschlüsselten Chats bei deiner nächsten Anmeldung wiederherzustellen."
"Du bist dabei, den Zugriff auf deine verschlüsselten Chats zu verlieren"
- "Du bist dabei, dich von deiner letzten Sitzung abzumelden. Wenn du dich jetzt abmeldest, verlierst du möglicherweise den Zugriff auf deine verschlüsselten Nachrichten."
+ "Dies ist dein einziges Gerät. Wenn du es entfernst, benötigst du einen Wiederherstellungsschlüssel, um deine digitale Identität zu bestätigen und deine verschlüsselten Chats bei deiner nächsten Anmeldung wiederherzustellen."
"Stelle sicher, dass du Zugriff auf deinen Wiederherstellungsschlüssel hast, bevor du dieses Gerät entfernst"
diff --git a/features/logout/impl/src/main/res/values-fa/translations.xml b/features/logout/impl/src/main/res/values-fa/translations.xml
index 8dfaad8580..d286ded539 100644
--- a/features/logout/impl/src/main/res/values-fa/translations.xml
+++ b/features/logout/impl/src/main/res/values-fa/translations.xml
@@ -1,16 +1,16 @@
- "مطمئنید که میخواهید از حسابتان خارج شوید؟"
- "خروج"
- "خروج"
- "خارج شدن…"
+ "مطمئنید که میخواهید این افزاره را بردارید؟"
+ "برداشتن این افزاره"
+ "برداشتن این افزاره"
+ "برداشتن افزاره…"
"دارید از واپسین نشستتان خارج میشوید. اگر اکنون خارج شوید پیامهای رمزنگاشتهتان را از دست خواهید داد."
"پشتیبان را خاموش کردهاید"
"در هنگامی که آفلاین شدید، کلیدهای شما هنوز در حال پشتیبانگیری بودند. دوباره متصل شوید ، تا قبل از خروج از کلیدهایتان نسخه پشتیبان گرفته شود."
"کلیدهایتان هنوز در حال پشتیبان گیریند"
"لطفاً پیش از خروج منتظر پایانش شوید."
"کلیدهایتان هنوز در حال پشتیبان گیریند"
- "خروج"
+ "برداشتن این افزاره"
"شما در آستانه خروج از آخرین جلسه خود هستید. اگر اکنون از سیستم خارج شوید، دسترسی به پیام های رمزگذاری شده تان را از دست خواهید داد."
"بازگردانی برپا نشده"
"دارید از واپسین نشستتان خارج میشوید. اگر اکنون خارج شوید ممکن است پیامهای رمزنگاشتهتان را از دست بدهید."
diff --git a/features/logout/impl/src/main/res/values-in/translations.xml b/features/logout/impl/src/main/res/values-in/translations.xml
index dabf83545e..9d63573973 100644
--- a/features/logout/impl/src/main/res/values-in/translations.xml
+++ b/features/logout/impl/src/main/res/values-in/translations.xml
@@ -1,16 +1,16 @@
- "Apakah Anda yakin ingin keluar dari akun?"
- "Keluar dari akun"
- "Keluar dari akun"
- "Mengeluarkan dari akun…"
+ "Apakah Anda yakin ingin non aktifkan device dari akun?"
+ "Hapus device dari akun"
+ "Hapus device dari akun"
+ "Mengeluarkan device dari akun…"
"Anda akan keluar dari sesi terakhir Anda. Jika Anda keluar sekarang, Anda akan kehilangan akses ke pesan terenkripsi Anda."
"Anda telah menonaktifkan pencadangan"
"Kunci Anda masih dicadangkan saat Anda luring. Sambungkan kembali sehingga kunci Anda dapat dicadangkan sebelum keluar."
"Kunci Anda masih dicadangkan"
"Mohon tunggu hingga proses ini selesai sebelum keluar."
"Kunci Anda masih dicadangkan"
- "Keluar dari akun"
+ "Hapus device dari akun"
"Anda akan keluar dari sesi Anda yang terakhir. Jika Anda keluar sekarang, Anda akan kehilangan akses ke pesan terenkripsi Anda."
"Pemulihan belum disiapkan"
"Anda akan keluar dari sesi terakhir Anda. Jika Anda keluar sekarang, Anda mungkin kehilangan akses ke pesan terenkripsi Anda."
diff --git a/features/logout/impl/src/main/res/values-pt/translations.xml b/features/logout/impl/src/main/res/values-pt/translations.xml
index b8a7161c21..76e4f09d42 100644
--- a/features/logout/impl/src/main/res/values-pt/translations.xml
+++ b/features/logout/impl/src/main/res/values-pt/translations.xml
@@ -1,18 +1,18 @@
- "Tens a certeza que queres terminar a sessão?"
- "Terminar sessão"
- "Terminar sessão"
- "A terminar sessão…"
- "Estás prestes a terminar a tua última sessão. Se continuares, perderás o acesso às tuas mensagens cifradas."
- "Desativaste a cópia de segurança"
- "As tuas chaves ainda estavam a ser guardadas quando ficaste desligado. Volta a ligar-te para que as tuas chaves possam ser guardadas antes de encerrares a sessão."
+ "Tens a certeza que queres remover este dispositivo?"
+ "Remover este dispositivo"
+ "Remover este dispositivo"
+ "A remover dispositivo…"
+ "Este é o teu único dispositivo. Se o removeres, da próxima vez que iniciares sessão, precisarás da chave de recuperação para confirmares a tua identidade digital e recuperares as tuas conversas cifradas."
+ "Estás prestes a perder o acesso às tuas conversas privadas"
+ "As tuas chaves ainda estavam a ser guardadas quando ficaste desligado. Volta a ligar-te para que as tuas chaves possam ser guardadas antes de removeres o dispositivo."
"As tuas chaves ainda estão a ser guardadas"
- "Por favor, aguarda a conclusão desta operação antes de terminares a sessão."
+ "Por favor, aguarda a conclusão desta operação antes de removeres o dispositivo."
"As tuas chaves ainda estão a ser guardadas"
- "Terminar sessão"
- "Estás prestes a terminar a tua última sessão. Se continuares, perderás o acesso às tuas mensagens cifradas."
- "Recuperação não configurada"
- "Estás prestes a terminar a tua última sessão. Se continuares, poderás perder o acesso às tuas mensagens cifradas."
- "Guardaste a tua chave de recuperação?"
+ "Remover este dispositivo"
+ "Este é o teu único dispositivo. Se o removeres, da próxima vez que iniciares sessão, precisarás da chave de recuperação para confirmares a tua identidade digital e recuperares as tuas conversas cifradas."
+ "Estás prestes a perder o acesso às tuas conversas cifradas"
+ "Este é o teu único dispositivo. Se o removeres, da próxima vez que iniciares sessão, precisarás da chave de recuperação para confirmares a tua identidade digital e recuperares as tuas conversas cifradas."
+ "Certifica-te de que tens acesso à tua chave de recuperação antes de removeres este dispositivo"
diff --git a/features/logout/impl/src/main/res/values-ro/translations.xml b/features/logout/impl/src/main/res/values-ro/translations.xml
index 7124188269..1f1ff9e07a 100644
--- a/features/logout/impl/src/main/res/values-ro/translations.xml
+++ b/features/logout/impl/src/main/res/values-ro/translations.xml
@@ -4,15 +4,15 @@
"Deconectați-vă"
"Deconectați-vă"
"Deconectare în curs…"
- "Sunteți pe cale să vă deconectați de la ultima sesiune. Dacă vă deconectați acum, veți pierde accesul la mesajele criptate."
- "Ați dezactivat backup-ul"
- "Cheile dumneavoastră erau încă în curs de backup atunci când ați fost deconectat. Reconectați-vă pentru ca cheile dumneavoastră să poată fi salvate înainte de a vă deconecta."
+ "Acesta este singurul dumneavoastră dispozitiv. Dacă îl eliminați, veți avea nevoie de o cheie de recuperare pentru a vă confirma identitatea digitală și a restaura mesajele criptate data viitoare când vă conectați."
+ "Sunteți pe cale să vă pierdeți accesul la mesajele dumneavoastră criptate."
+ "Cheile dumneavoastră erau încă în curs de backup atunci când ați fost deconectat. Reconectați-vă pentru ca cheile dumneavoastră să poată fi salvate înainte de a elimina acest dispozitiv."
"Cheile dumneavoastră sunt încă în curs de backup"
"Vă rugăm să așteptați până la finalizarea acestui proces înainte de a vă deconecta."
"Cheile dumneavoastră sunt încă în curs de backup"
"Deconectați-vă"
- "Sunteți pe cale să vă deconectați de la ultima sesiune. Dacă vă deconectați acum, veți pierde accesul la mesajele criptate."
- "Recuperarea nu este configurată"
- "Sunteți pe cale să vă deconectați de la ultima sesiune. Dacă vă deconectați acum, este posibil să pierdeți accesul la mesajele criptate."
- "Ați salvat cheia de recuperare?"
+ "Acesta este singurul dumneavoastră dispozitiv. Dacă îl eliminați, veți avea nevoie de o cheie de recuperare pentru a vă confirma identitatea digitală și a restaura chat-urile criptate data viitoare când vă conectați."
+ "Sunteți pe cale să pierdeți accesul la mesajele dumneavoastră criptate"
+ "Acesta este singurul dumneavoastră dispozitiv. Dacă îl eliminați, veți avea nevoie de o cheie de recuperare pentru a vă confirma identitatea digitală și a restaura mesajele criptate data viitoare când vă conectați."
+ "Asigurați-vă că aveți acces la cheia de recuperare înainte de a elimina acest dispozitiv."
diff --git a/features/logout/impl/src/main/res/values-zh/translations.xml b/features/logout/impl/src/main/res/values-zh/translations.xml
index d438c64ff3..f18904d03b 100644
--- a/features/logout/impl/src/main/res/values-zh/translations.xml
+++ b/features/logout/impl/src/main/res/values-zh/translations.xml
@@ -1,18 +1,18 @@
- "您确定要删除此设备吗?"
- "删除此设备"
- "删除此设备"
- "正在删除设备……"
- "即将登出最后一个会话。如果现在登出,将无法访问加密的消息。"
- "您已关闭备份"
- "当你离线时,密钥仍在备份中。重新连接以便在登出之前备份密钥。"
- "您的密钥仍在备份中"
- "请等待此操作完成后再登出。"
- "您的密钥仍在备份中"
- "删除此设备"
- "即将登出最后一个会话。如果现在登出,将无法访问加密的消息。"
- "未设置恢复"
- "即将登出最后一个会话。如果现在登出,将无法访问加密的消息。"
- "您保存了恢复密钥吗?"
+ "你确定要移除此设备?"
+ "移除此设备"
+ "移除此设备"
+ "正在移除设备…"
+ "这是你的唯一设备。一旦移除,下次登录时你需要使用恢复密钥验证数字身份并恢复加密聊天。"
+ "你即将失去加密聊天的访问权"
+ "当你离线时,密钥仍在备份。重新连接以便在移除设备之前备份密钥。"
+ "你的密钥仍在备份中"
+ "请等待此操作完成再移除此设备。"
+ "你的密钥仍在备份中"
+ "移除此设备"
+ "这是你的唯一设备。一旦移除,下次登录时你需要使用恢复密钥验证数字身份并恢复加密聊天。"
+ "你即将失去加密聊天的访问权"
+ "这是你的唯一设备。一旦移除,下次登录时你需要使用恢复密钥验证数字身份并恢复加密聊天。"
+ "确保你移除此设备前拥有恢复密钥"
diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt
index 84ca038d7b..a42fd891d4 100644
--- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt
+++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.logout.impl
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.testtags.TestTags
@@ -21,97 +24,93 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressTag
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LogoutViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on logout sends a LogoutEvents`() {
+ fun `clicking on logout sends a LogoutEvents`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLogoutView(
+ setLogoutView(
aLogoutState(
eventSink = eventsRecorder
),
)
- rule.clickOn(CommonStrings.action_signout)
+ clickOn(CommonStrings.action_signout)
eventsRecorder.assertSingle(LogoutEvents.Logout(false))
}
@Test
- fun `confirming logout sends a LogoutEvents`() {
+ fun `confirming logout sends a LogoutEvents`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLogoutView(
+ setLogoutView(
aLogoutState(
logoutAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder
),
)
- rule.pressTag(TestTags.dialogPositive.value)
+ pressTag(TestTags.dialogPositive.value)
eventsRecorder.assertSingle(LogoutEvents.Logout(false))
}
@Test
- fun `clicking on back invoke back callback`() {
+ fun `clicking on back invoke back callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setLogoutView(
+ setLogoutView(
aLogoutState(
eventSink = eventsRecorder
),
onBackClick = callback,
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `clicking on confirm after error sends a LogoutEvents`() {
+ fun `clicking on confirm after error sends a LogoutEvents`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLogoutView(
+ setLogoutView(
aLogoutState(
logoutAction = AsyncAction.Failure(Exception("Failed to logout")),
eventSink = eventsRecorder
),
)
- rule.clickOn(CommonStrings.action_signout_anyway)
+ clickOn(CommonStrings.action_signout_anyway)
eventsRecorder.assertSingle(LogoutEvents.Logout(true))
}
@Test
- fun `clicking on cancel after error sends a LogoutEvents`() {
+ fun `clicking on cancel after error sends a LogoutEvents`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLogoutView(
+ setLogoutView(
aLogoutState(
logoutAction = AsyncAction.Failure(Exception("Failed to logout")),
eventSink = eventsRecorder
),
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(LogoutEvents.CloseDialogs)
}
@Test
- fun `last session setting button invoke onChangeRecoveryKeyClicked`() {
+ fun `last session setting button invoke onChangeRecoveryKeyClicked`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
ensureCalledOnce { callback ->
- rule.setLogoutView(
+ setLogoutView(
aLogoutState(
isLastDevice = true,
eventSink = eventsRecorder
),
onChangeRecoveryKeyClick = callback,
)
- rule.clickOn(CommonStrings.common_settings)
+ clickOn(CommonStrings.common_settings)
}
}
}
-private fun AndroidComposeTestRule.setLogoutView(
+private fun AndroidComposeUiTest.setLogoutView(
state: LogoutState,
onChangeRecoveryKeyClick: () -> Unit = EnsureNeverCalled(),
onBackClick: () -> Unit = EnsureNeverCalled(),
diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt
index 8eae534740..99860259c4 100644
--- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt
+++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.logout.impl.direct
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.logout.api.direct.DirectLogoutEvents
import io.element.android.features.logout.api.direct.DirectLogoutState
@@ -21,83 +24,79 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.pressBackKey
import org.junit.Ignore
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DefaultDirectLogoutViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on confirm logout sends expected Event`() {
+ fun `clicking on confirm logout sends expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDefaultDirectLogoutView(
+ setDefaultDirectLogoutView(
state = aDirectLogoutState(
logoutAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder,
)
)
- rule.clickOn(CommonStrings.action_signout)
+ clickOn(CommonStrings.action_signout)
eventsRecorder.assertSingle(DirectLogoutEvents.Logout(false))
}
@Test
- fun `clicking on cancel logout sends expected Event`() {
+ fun `clicking on cancel logout sends expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDefaultDirectLogoutView(
+ setDefaultDirectLogoutView(
state = aDirectLogoutState(
logoutAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder,
)
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
}
@Ignore("Pressing back key should dismiss the dialog, and so generate the expected event, but it's not the case.")
@Test
- fun `clicking on back invoke back callback`() {
+ fun `clicking on back invoke back callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDefaultDirectLogoutView(
+ setDefaultDirectLogoutView(
state = aDirectLogoutState(
logoutAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder,
)
)
- rule.pressBackKey()
+ pressBackKey()
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
}
@Test
- fun `clicking on confirm after error sends expected Event`() {
+ fun `clicking on confirm after error sends expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDefaultDirectLogoutView(
+ setDefaultDirectLogoutView(
state = aDirectLogoutState(
logoutAction = AsyncAction.Failure(Exception("Error")),
eventSink = eventsRecorder,
)
)
- rule.clickOn(CommonStrings.action_signout_anyway)
+ clickOn(CommonStrings.action_signout_anyway)
eventsRecorder.assertSingle(DirectLogoutEvents.Logout(true))
}
@Test
- fun `clicking on cancel after error sends expected Event`() {
+ fun `clicking on cancel after error sends expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setDefaultDirectLogoutView(
+ setDefaultDirectLogoutView(
state = aDirectLogoutState(
logoutAction = AsyncAction.Failure(Exception("Error")),
eventSink = eventsRecorder,
)
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
}
}
-private fun AndroidComposeTestRule.setDefaultDirectLogoutView(
+private fun AndroidComposeUiTest.setDefaultDirectLogoutView(
state: DirectLogoutState,
) {
setContent {
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt
index 36e94ec456..7b0515a423 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt
@@ -24,7 +24,7 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.annotations.ContributesNode
-import io.element.android.features.call.api.CallType
+import io.element.android.features.call.api.CallData
import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.features.forward.api.ForwardEntryPoint
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
@@ -143,6 +143,7 @@ class MessagesFlowNode(
val mediaInfo: MediaInfo,
val mediaSource: MediaSource,
val thumbnailSource: MediaSource?,
+ val canUseOverlay: Boolean,
) : NavTarget
@Parcelize
@@ -227,10 +228,11 @@ class MessagesFlowNode(
callback.navigateToRoomDetails()
}
- override fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean {
+ override fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, canUseOverlay: Boolean): Boolean {
return processEventClick(
timelineMode = timelineMode,
event = event,
+ canUseOverlay = canUseOverlay,
)
}
@@ -277,13 +279,13 @@ class MessagesFlowNode(
}
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
- val callType = CallType.RoomCall(
+ val callData = CallData(
sessionId = sessionId,
roomId = roomId,
- isAudioCall = isAudioCall
+ isAudioCall = isAudioCall,
)
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
- elementCallEntryPoint.startCall(callType)
+ elementCallEntryPoint.startCall(callData)
}
override fun navigateToPinnedMessagesList() {
@@ -320,7 +322,11 @@ class MessagesFlowNode(
)
val callback = object : MediaViewerEntryPoint.Callback {
override fun onDone() {
- overlay.hide()
+ if (navTarget.canUseOverlay) {
+ overlay.hide()
+ } else {
+ backstack.pop()
+ }
}
override fun viewInTimeline(eventId: EventId) {
@@ -414,10 +420,11 @@ class MessagesFlowNode(
}
NavTarget.PinnedMessagesList -> {
val callback = object : PinnedMessagesListNode.Callback {
- override fun handleEventClick(event: TimelineItem.Event) {
+ override fun handleEventClick(event: TimelineItem.Event, canUseOverlay: Boolean) {
processEventClick(
timelineMode = Timeline.Mode.PinnedEvents,
event = event,
+ canUseOverlay = canUseOverlay,
)
}
@@ -456,10 +463,11 @@ class MessagesFlowNode(
focusedEventId = navTarget.focusedEventId,
)
val callback = object : ThreadedMessagesNode.Callback {
- override fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean {
+ override fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, canUseOverlay: Boolean): Boolean {
return processEventClick(
timelineMode = timelineMode,
event = event,
+ canUseOverlay = canUseOverlay,
)
}
@@ -506,13 +514,13 @@ class MessagesFlowNode(
}
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
- val callType = CallType.RoomCall(
+ val callData = CallData(
sessionId = sessionId,
roomId = roomId,
isAudioCall = isAudioCall
)
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
- elementCallEntryPoint.startCall(callType)
+ elementCallEntryPoint.startCall(callData)
}
override fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) {
@@ -547,6 +555,7 @@ class MessagesFlowNode(
private fun processEventClick(
timelineMode: Timeline.Mode,
event: TimelineItem.Event,
+ canUseOverlay: Boolean,
): Boolean {
val navTarget = when (event.content) {
is TimelineItemImageContent -> {
@@ -556,6 +565,7 @@ class MessagesFlowNode(
content = event.content,
mediaSource = event.content.mediaSource,
thumbnailSource = event.content.thumbnailSource,
+ canUseOverlay = canUseOverlay,
)
}
is TimelineItemVideoContent -> {
@@ -565,6 +575,7 @@ class MessagesFlowNode(
content = event.content,
mediaSource = event.content.mediaSource,
thumbnailSource = event.content.thumbnailSource,
+ canUseOverlay = canUseOverlay,
)
}
is TimelineItemFileContent -> {
@@ -574,6 +585,7 @@ class MessagesFlowNode(
content = event.content,
mediaSource = event.content.mediaSource,
thumbnailSource = event.content.thumbnailSource,
+ canUseOverlay = canUseOverlay,
)
}
is TimelineItemAudioContent -> {
@@ -583,6 +595,7 @@ class MessagesFlowNode(
content = event.content,
mediaSource = event.content.mediaSource,
thumbnailSource = null,
+ canUseOverlay = canUseOverlay,
)
}
is TimelineItemLocationContent -> {
@@ -603,7 +616,11 @@ class MessagesFlowNode(
}
return when (navTarget) {
is NavTarget.MediaViewer -> {
- overlay.show(navTarget)
+ if (canUseOverlay) {
+ overlay.show(navTarget)
+ } else {
+ backstack.push(navTarget)
+ }
true
}
is NavTarget.LocationViewer -> {
@@ -620,6 +637,7 @@ class MessagesFlowNode(
content: TimelineItemEventContentWithAttachment,
mediaSource: MediaSource,
thumbnailSource: MediaSource?,
+ canUseOverlay: Boolean,
): NavTarget {
return NavTarget.MediaViewer(
mode = mode,
@@ -647,6 +665,7 @@ class MessagesFlowNode(
),
mediaSource = mediaSource,
thumbnailSource = thumbnailSource,
+ canUseOverlay = canUseOverlay,
)
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt
index a2cf4a3da0..308cda506e 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt
@@ -68,6 +68,8 @@ import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.mediaplayer.api.MediaPlayer
import io.element.android.libraries.ui.strings.CommonStrings
+import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.LoadMessagesUi
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.api.finishLongRunningTransaction
@@ -115,7 +117,7 @@ class MessagesNode(
)
interface Callback : Plugin {
- fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean
+ fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, canUseOverlay: Boolean): Boolean
fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?)
fun navigateToRoomMemberDetails(userId: UserId)
fun handlePermalinkClick(data: PermalinkData)
@@ -247,6 +249,7 @@ class MessagesNode(
override fun View(modifier: Modifier) {
val activity = requireNotNull(LocalActivity.current)
val isDark = ElementTheme.isLightTheme.not()
+ val canUseOverlay = !isTalkbackActive() && !hasExternalKeyboard()
CompositionLocalProvider(
LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories,
) {
@@ -268,11 +271,11 @@ class MessagesNode(
onRoomDetailsClick = callback::navigateToRoomDetails,
onEventContentClick = { isLive, event ->
if (isLive) {
- callback.handleEventClick(timelineController.mainTimelineMode(), event)
+ callback.handleEventClick(timelineController.mainTimelineMode(), event, canUseOverlay)
} else {
val detachedTimelineMode = timelineController.detachedTimelineMode()
if (detachedTimelineMode != null) {
- callback.handleEventClick(detachedTimelineMode, event)
+ callback.handleEventClick(detachedTimelineMode, event, canUseOverlay)
} else {
false
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt
index f115dd2799..5271906fff 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt
@@ -217,12 +217,10 @@ class MessagesPresenter(
val dmRoomMember by room.getDirectRoomMember(membersState)
val roomMemberIdentityStateChanges = identityChangeState.roomMemberIdentityStateChanges
- val isKeyShareOnInviteEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false)
// The top bar should show a "history" icon if:
- // * History sharing is enabled,
// * The room is encrypted, and:
// * The room's history_visibility allows future users to see content.
- val topBarSharedHistoryIcon = if (isKeyShareOnInviteEnabled) roomInfo.sharedHistoryIcon() else SharedHistoryIcon.NONE
+ val topBarSharedHistoryIcon = roomInfo.sharedHistoryIcon()
LifecycleResumeEffect(dmRoomMember, roomInfo.isEncrypted) {
if (roomInfo.isEncrypted == true) {
@@ -274,6 +272,8 @@ class MessagesPresenter(
}
}
navigator.close()
+ }.invokeOnCompletion {
+ markingAsReadAndExiting.set(false)
}
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
index 16021df3e9..14c83db833 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
@@ -44,6 +44,7 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
@@ -94,8 +95,8 @@ open class MessagesStateProvider : PreviewParameterProvider {
}
fun aMessagesState(
- roomName: String? = "Room name",
- roomAvatar: AvatarData = AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom),
+ roomName: String? = ROOM_NAME,
+ roomAvatar: AvatarData = AvatarData("!id:domain", ROOM_NAME, size = AvatarSize.TimelineRoom),
userEventPermissions: UserEventPermissions = aUserEventPermissions(),
composerState: MessageComposerState = aMessageComposerState(
textEditorState = aTextEditorStateRich(initialText = "Hello", initialFocus = true),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt
index ef446e36cf..e10dd2e1bc 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt
@@ -156,6 +156,7 @@ fun ActionListView(
sheetState = sheetState,
onDismissRequest = ::onDismiss,
modifier = modifier,
+ scrollable = false,
) {
ActionListViewContent(
state = state,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt
index 47d1947766..be53de5f66 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt
@@ -11,6 +11,7 @@ package io.element.android.features.messages.impl.crypto.identity
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.ui.room.IdentityRoomMember
@@ -32,7 +33,7 @@ class IdentityChangeStateProvider : PreviewParameterProvider {
override val values: Sequence
@@ -37,10 +38,10 @@ fun aResolveVerifiedUserSendFailureState(
eventSink = eventSink
)
-fun anUnsignedDeviceSendFailure(userDisplayName: String = "Alice") = VerifiedUserSendFailure.UnsignedDevice.FromOther(
+fun anUnsignedDeviceSendFailure(userDisplayName: String = USER_NAME_ALICE) = VerifiedUserSendFailure.UnsignedDevice.FromOther(
userDisplayName = userDisplayName,
)
-fun aChangedIdentitySendFailure(userDisplayName: String = "Alice") = VerifiedUserSendFailure.ChangedIdentity(
+fun aChangedIdentitySendFailure(userDisplayName: String = USER_NAME_ALICE) = VerifiedUserSendFailure.ChangedIdentity(
userDisplayName = userDisplayName,
)
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt
index 98e2bba3be..c14dd03f1e 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt
@@ -75,6 +75,7 @@ fun ResolveVerifiedUserSendFailureView(
.navigationBarsPadding(),
sheetState = sheetState,
onDismissRequest = ::dismiss,
+ scrollable = true,
) {
IconTitleSubtitleMolecule(
modifier = Modifier.padding(24.dp),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt
index 1fdb61f484..405e2b0be9 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt
@@ -13,6 +13,8 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@@ -74,7 +76,8 @@ internal fun AttachmentsBottomSheet(
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
- onDismissRequest = { isVisible = false }
+ onDismissRequest = { isVisible = false },
+ scrollable = false,
) {
AttachmentSourcePickerMenu(
state = state,
@@ -97,6 +100,7 @@ private fun AttachmentSourcePickerMenu(
modifier = Modifier
.navigationBarsPadding()
.imePadding()
+ .verticalScroll(rememberScrollState())
) {
ListItem(
modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) },
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt
index 678ef2ba56..3d18db12d9 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt
@@ -33,6 +33,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType.Ro
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.RoomAlias
@@ -198,7 +199,7 @@ internal fun SuggestionsPickerViewPreview() {
suggestions = persistentListOf(
ResolvedSuggestion.AtRoom,
ResolvedSuggestion.Member(roomMember),
- ResolvedSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")),
+ ResolvedSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = USER_NAME_BOB)),
ResolvedSuggestion.Alias(
roomAlias = anAlias,
roomId = RoomId("!room:matrix.org"),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt
index cddc1831db..a618117950 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt
@@ -38,6 +38,8 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.ui.strings.CommonStrings
+import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
@ContributesNode(RoomScope::class)
@AssistedInject
@@ -50,7 +52,7 @@ class PinnedMessagesListNode(
private val permalinkParser: PermalinkParser,
) : Node(buildContext, plugins = plugins), PinnedMessagesListNavigator {
interface Callback : Plugin {
- fun handleEventClick(event: TimelineItem.Event)
+ fun handleEventClick(event: TimelineItem.Event, canUseOverlay: Boolean)
fun navigateToRoomMemberDetails(userId: UserId)
fun viewInTimeline(eventId: EventId)
fun handlePermalinkClick(data: PermalinkData.RoomLink)
@@ -103,6 +105,7 @@ class PinnedMessagesListNode(
@Composable
override fun View(modifier: Modifier) {
+ val canUseOverlay = !isTalkbackActive() && !hasExternalKeyboard()
CompositionLocalProvider(
LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories,
) {
@@ -113,7 +116,9 @@ class PinnedMessagesListNode(
PinnedMessagesListView(
state = state,
onBackClick = ::navigateUp,
- onEventClick = callback::handleEventClick,
+ onEventClick = {
+ callback.handleEventClick(it, canUseOverlay)
+ },
onUserDataClick = { callback.navigateToRoomMemberDetails(it.userId) },
onLinkClick = { link -> onLinkClick(context, link.url) },
onLinkLongClick = {
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt
index 0949237862..0c58316b5e 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt
@@ -68,6 +68,8 @@ import io.element.android.libraries.matrix.api.room.alias.matches
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.mediaplayer.api.MediaPlayer
+import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.collections.immutable.ImmutableList
@@ -124,7 +126,7 @@ class ThreadedMessagesNode(
}
interface Callback : Plugin {
- fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean
+ fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, canUseOverlay: Boolean): Boolean
fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?)
fun navigateToRoomMemberDetails(userId: UserId)
fun handlePermalinkClick(data: PermalinkData)
@@ -252,6 +254,7 @@ class ThreadedMessagesNode(
override fun View(modifier: Modifier) {
val activity = requireNotNull(LocalActivity.current)
val isDark = ElementTheme.isLightTheme.not()
+ val canUseOverlay = !isTalkbackActive() && !hasExternalKeyboard()
CompositionLocalProvider(
LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories,
) {
@@ -271,11 +274,11 @@ class ThreadedMessagesNode(
onEventContentClick = { isLive, event ->
timelineController?.let { controller ->
if (isLive) {
- callback.handleEventClick(controller.mainTimelineMode(), event)
+ callback.handleEventClick(controller.mainTimelineMode(), event, canUseOverlay)
} else {
val detachedTimelineMode = controller.detachedTimelineMode()
if (detachedTimelineMode != null) {
- callback.handleEventClick(detachedTimelineMode, event)
+ callback.handleEventClick(detachedTimelineMode, event, canUseOverlay)
} else {
false
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListView.kt
index c93af5c162..1beabcd3fa 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListView.kt
@@ -43,6 +43,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
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.Scaffold
@@ -303,7 +305,7 @@ internal fun ThreadsListViewPreview() {
ThreadsListView(
state = ThreadsListState(
roomId = RoomId("!room-id:server"),
- roomName = "Room name",
+ roomName = ROOM_NAME,
roomAvatarUrl = null,
threads = List(10) { aThreadListRowItem(threadId = ThreadId("\$thread-$it")) }.toImmutableList(),
isRoomTombstoned = false,
@@ -360,7 +362,7 @@ fun aThreadListItem(
fun aThreadListItemEvent(
threadId: ThreadId = ThreadId("\$a-thread-id"),
senderId: UserId = UserId("@a-user-id:server"),
- senderProfile: ProfileDetails = ProfileDetails.Ready(displayName = "Alice", displayNameAmbiguous = false, avatarUrl = null),
+ senderProfile: ProfileDetails = ProfileDetails.Ready(displayName = USER_NAME_ALICE, displayNameAmbiguous = false, avatarUrl = null),
isOwn: Boolean = false,
content: EventContent = MessageContent(
body = "Hello world!",
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt
index 9840ac5107..0bf293eca4 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt
@@ -29,6 +29,8 @@ import io.element.android.features.messages.impl.typing.aTypingNotificationState
import io.element.android.features.roomcall.api.aStandByCallState
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
+import io.element.android.libraries.designsystem.preview.USER_NAME_SENDER
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UniqueId
@@ -143,7 +145,7 @@ internal fun aTimelineItemEvent(
isMine: Boolean = false,
isEditable: Boolean = false,
canBeRepliedTo: Boolean = false,
- senderDisplayName: String = "Sender",
+ senderDisplayName: String = USER_NAME_SENDER,
displayNameAmbiguous: Boolean = false,
content: TimelineItemEventContent = aTimelineItemTextContent(),
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
@@ -160,7 +162,7 @@ internal fun aTimelineItemEvent(
eventId = eventId,
transactionId = transactionId,
senderId = UserId("@senderId:domain"),
- senderAvatar = AvatarData("@senderId:domain", "sender", size = AvatarSize.TimelineSender),
+ senderAvatar = AvatarData("@senderId:domain", USER_NAME_SENDER, size = AvatarSize.TimelineSender),
content = content,
reactionsState = timelineItemReactions,
readReceiptState = readReceiptState,
@@ -253,7 +255,7 @@ internal fun aGroupedEvents(
}
internal fun aTimelineRoomInfo(
- name: String = "Room name",
+ name: String = ROOM_NAME,
isDm: Boolean = false,
userHasPermissionToSendMessage: Boolean = true,
pinnedEventIds: List = emptyList(),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
index 2105cf9df7..41a828abb4 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
@@ -77,7 +77,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
-import io.element.android.libraries.ui.utils.time.isTalkbackActive
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.wysiwyg.link.Link
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt
index aa5aaa2075..80bd342d01 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt
@@ -47,8 +47,8 @@ import io.element.android.libraries.designsystem.theme.messageFromMeBackground
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.libraries.ui.utils.graphics.drawInLayer
-import io.element.android.libraries.ui.utils.time.isTalkbackActive
private val BUBBLE_RADIUS = 12.dp
private val avatarRadius = AvatarSize.TimelineSender.dp / 2
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt
index 096ab018e2..1859e66750 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt
@@ -26,6 +26,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.R
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.UserId
@@ -162,7 +163,7 @@ internal fun MessageShieldViewPreview() {
MessageShield.AuthenticityNotGuaranteed(false),
forwarder = UserId("@alice:example.com"),
forwarderProfile = ProfileDetails.Ready(
- displayName = "Alice",
+ displayName = USER_NAME_ALICE,
displayNameAmbiguous = false,
avatarUrl = null,
),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt
index 21ef7c5b09..25a53cec2d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt
@@ -15,9 +15,12 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@@ -47,9 +50,39 @@ fun TimelineEventTimestampView(
val isMessageEdited = event.content.isEdited()
val isMessageRedacted = event.content.isRedacted()
val tint = if (hasError || hasEncryptionCritical && !isMessageRedacted) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textSecondary
+
+ val shield = event.messageShield
+ val isVerifiedUserSendFailure = event.localSendState is LocalEventSendState.Failed.VerifiedUser
+ val onClickLabel = when {
+ shield != null -> stringResource(CommonStrings.a11y_view_details)
+ hasError && isVerifiedUserSendFailure -> stringResource(CommonStrings.action_open_context_menu)
+ else -> null
+ }
+ val clickableModifier = remember(shield, hasError) {
+ when {
+ shield != null -> {
+ Modifier.clickable(
+ onClickLabel = onClickLabel,
+ ) {
+ eventSink(TimelineEvent.ShowShieldDialog(shield))
+ }
+ }
+ hasError -> Modifier
+ .clickable(
+ enabled = isVerifiedUserSendFailure,
+ onClickLabel = onClickLabel,
+ ) {
+ eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event))
+ }
+ else -> Modifier
+ }
+ }
Row(
modifier = Modifier
.padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing))
+ // For a better click target, make the corners rounded
+ .clip(RoundedCornerShape(8.dp))
+ .then(clickableModifier)
.then(modifier),
verticalAlignment = Alignment.CenterVertically,
) {
@@ -67,36 +100,22 @@ fun TimelineEventTimestampView(
color = tint,
)
if (hasError) {
- val isVerifiedUserSendFailure = event.localSendState is LocalEventSendState.Failed.VerifiedUser
Spacer(modifier = Modifier.width(2.dp))
Icon(
imageVector = CompoundIcons.ErrorSolid(),
contentDescription = stringResource(id = CommonStrings.common_sending_failed),
tint = tint,
- modifier = Modifier
- .size(15.dp, 18.dp)
- .clickable(
- enabled = isVerifiedUserSendFailure,
- onClickLabel = stringResource(CommonStrings.action_open_context_menu),
- ) {
- eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event))
- }
+ modifier = Modifier.size(15.dp, 18.dp),
)
}
if (!isMessageRedacted) {
- event.messageShield?.let { shield ->
+ shield?.let { shield ->
Spacer(modifier = Modifier.width(2.dp))
Icon(
imageVector = shield.toIcon(),
contentDescription = stringResource(id = CommonStrings.a11y_encryption_details),
- modifier = Modifier
- .size(15.dp)
- .clickable(
- onClickLabel = stringResource(CommonStrings.a11y_view_details),
- ) {
- eventSink(TimelineEvent.ShowShieldDialog(shield))
- },
+ modifier = Modifier.size(15.dp),
tint = shield.toIconColor(),
)
Spacer(modifier = Modifier.width(4.dp))
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt
index 976fa3c17e..5785627564 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt
@@ -92,6 +92,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.modifiers.niceClickable
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.designsystem.swipe.SwipeableActionsState
import io.element.android.libraries.designsystem.swipe.rememberSwipeableActionsState
import io.element.android.libraries.designsystem.text.toPx
@@ -120,7 +121,7 @@ import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonPlurals
import io.element.android.libraries.ui.strings.CommonStrings
-import io.element.android.libraries.ui.utils.time.isTalkbackActive
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.wysiwyg.link.Link
import kotlinx.coroutines.launch
import kotlin.math.abs
@@ -863,7 +864,7 @@ internal fun TimelineItemEventRowWithThreadSummaryPreview() = ElementPreview {
),
senderId = UserId("@user:id"),
senderProfile = ProfileDetails.Ready(
- displayName = "Alice",
+ displayName = USER_NAME_ALICE,
avatarUrl = null,
displayNameAmbiguous = false,
),
@@ -898,7 +899,7 @@ internal fun ThreadSummaryViewPreview() {
),
senderId = UserId("@user:id"),
senderProfile = ProfileDetails.Ready(
- displayName = "Alice",
+ displayName = USER_NAME_ALICE,
avatarUrl = null,
displayNameAmbiguous = true,
),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowUtdPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowUtdPreview.kt
index 49328ac025..abacc45ef8 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowUtdPreview.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowUtdPreview.kt
@@ -16,6 +16,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemGrou
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause
@@ -25,7 +27,7 @@ internal fun TimelineItemEventRowUtdPreview() = ElementPreview {
Column {
ATimelineItemEventRow(
event = aTimelineItemEvent(
- senderDisplayName = "Alice",
+ senderDisplayName = USER_NAME_ALICE,
isMine = false,
content = TimelineItemEncryptedContent(
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
@@ -39,7 +41,7 @@ internal fun TimelineItemEventRowUtdPreview() = ElementPreview {
)
ATimelineItemEventRow(
event = aTimelineItemEvent(
- senderDisplayName = "Bob",
+ senderDisplayName = USER_NAME_BOB,
isMine = false,
content = TimelineItemEncryptedContent(
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
@@ -54,7 +56,7 @@ internal fun TimelineItemEventRowUtdPreview() = ElementPreview {
ATimelineItemEventRow(
event = aTimelineItemEvent(
- senderDisplayName = "Bob",
+ senderDisplayName = USER_NAME_BOB,
isMine = false,
content = TimelineItemEncryptedContent(
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt
index 8316911843..df2b9d8691 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt
@@ -34,7 +34,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.user.MatrixUser
-import io.element.android.libraries.ui.utils.time.isTalkbackActive
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.wysiwyg.link.Link
@Composable
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt
index e75df2f89f..842b7a08f5 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt
@@ -47,7 +47,7 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.ui.strings.CommonStrings
-import io.element.android.libraries.ui.utils.time.isTalkbackActive
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.wysiwyg.link.Link
import kotlin.time.DurationUnit
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt
index f42a26cf65..4ac83c520b 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt
@@ -50,7 +50,8 @@ fun CustomReactionBottomSheet(
ModalBottomSheet(
onDismissRequest = ::onDismiss,
sheetState = sheetState,
- modifier = modifier
+ modifier = modifier,
+ scrollable = false,
) {
val presenter = remember {
EmojiPickerPresenter(
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt
index a8cbb89e96..0e0a98a96c 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt
@@ -54,10 +54,11 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
import io.element.android.libraries.ui.strings.CommonStrings
-import io.element.android.libraries.ui.utils.time.isTalkbackActive
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.wysiwyg.compose.EditorStyledText
import io.element.android.wysiwyg.link.Link
+private const val TALL_IMAGE_RATIO_DIVISOR = 3
@Composable
fun TimelineItemImageView(
content: TimelineItemImageContent,
@@ -79,7 +80,7 @@ fun TimelineItemImageView(
Modifier
}
TimelineItemAspectRatioBox(
- modifier = containerModifier.blurHashBackground(content.blurhash, alpha = 0.9f),
+ modifier = containerModifier.blurHashBackground(content.blurhash, alpha = 0.9f).align(Alignment.CenterHorizontally),
aspectRatio = coerceRatioWhenHidingContent(content.aspectRatio, hideMediaContent),
) {
ProtectedView(
@@ -123,7 +124,14 @@ fun TimelineItemImageView(
LocalContentColor provides ElementTheme.colors.textPrimary,
LocalTextStyle provides ElementTheme.typography.fontBodyLgRegular
) {
- val aspectRatio = content.aspectRatio ?: DEFAULT_ASPECT_RATIO
+ val width = content.width ?: 0
+ val height = content.height ?: 0
+ // if image is narrow and tall use DEFAULT_ASPECT_RATIO
+ val aspectRatio = if (width < height / TALL_IMAGE_RATIO_DIVISOR) {
+ DEFAULT_ASPECT_RATIO
+ } else {
+ content.aspectRatio ?: DEFAULT_ASPECT_RATIO
+ }
EditorStyledText(
modifier = Modifier
.padding(horizontal = 4.dp) // This is (12.dp - 8.dp) contentPadding from CommonLayout
@@ -200,3 +208,38 @@ internal fun TimelineImageWithCaptionRowPreview() = ElementPreview {
)
}
}
+
+@PreviewsDayNight
+@Composable
+internal fun ATimelineItemEventRowPreview() = ElementPreview {
+ Column {
+ sequenceOf(false, true).forEach { isMine ->
+ ATimelineItemEventRow(
+ event = aTimelineItemEvent(
+ isMine = isMine,
+ content = aTimelineItemImageContent(
+ filename = "image.jpg",
+ caption = "A long caption that may wrap into several lines",
+ width = 80,
+ height = 300,
+ aspectRatio = 80f / 300f,
+ ),
+ groupPosition = TimelineItemGroupPosition.Last,
+ ),
+ )
+ }
+ ATimelineItemEventRow(
+ event = aTimelineItemEvent(
+ isMine = false,
+ content = aTimelineItemImageContent(
+ filename = "image.jpg",
+ caption = "Narrow image with null aspectRatio",
+ width = 80,
+ height = 300,
+ aspectRatio = null,
+ ),
+ groupPosition = TimelineItemGroupPosition.Last,
+ ),
+ )
+ }
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt
index f5e760736e..8d1ef18f39 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt
@@ -64,7 +64,7 @@ import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_WIDTH
import io.element.android.libraries.matrix.ui.media.MediaRequestData
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
import io.element.android.libraries.ui.strings.CommonStrings
-import io.element.android.libraries.ui.utils.time.isTalkbackActive
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.wysiwyg.compose.EditorStyledText
import io.element.android.wysiwyg.link.Link
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt
index 5dbd0c478f..86e3f1c849 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt
@@ -52,7 +52,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
-import io.element.android.libraries.ui.utils.time.isTalkbackActive
+import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvent
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import io.element.android.libraries.voiceplayer.api.VoiceMessageStateProvider
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt
index 03ce564eff..2a902fd692 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt
@@ -90,7 +90,8 @@ fun ReactionSummaryView(
if (state.target != null) {
ModalBottomSheet(
onDismissRequest = ::onDismiss,
- modifier = modifier
+ modifier = modifier,
+ scrollable = false,
) {
ReactionSummaryViewContent(summary = state.target)
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt
index b65e326045..9637298d58 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt
@@ -57,7 +57,8 @@ internal fun ReadReceiptBottomSheet(
sheetState.hide()
state.eventSink(ReadReceiptBottomSheetEvent.Dismiss)
}
- }
+ },
+ scrollable = false,
) {
ReadReceiptBottomSheetContent(
state = state,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt
index f812b40e61..d59526d8bb 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt
@@ -24,6 +24,7 @@ import io.element.android.features.messages.impl.R
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
import io.element.android.libraries.designsystem.text.toAnnotatedString
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.allBooleans
@@ -86,13 +87,13 @@ internal fun TimelineItemRoomBeginningViewPreview() = ElementPreview {
)
TimelineItemRoomBeginningView(
predecessorRoom = null,
- roomName = "Room Name",
+ roomName = ROOM_NAME,
isDm = isDm,
onPredecessorRoomClick = {},
)
TimelineItemRoomBeginningView(
predecessorRoom = PredecessorRoom(RoomId("!roomId:matrix.org")),
- roomName = "Room Name",
+ roomName = ROOM_NAME,
isDm = isDm,
onPredecessorRoomClick = {},
)
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt
index d07d5db6a4..6d3b114428 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt
@@ -28,6 +28,8 @@ fun aTimelineItemImageContent(
blurhash: String? = A_BLUR_HASH,
filename: String = "A picture.jpg",
caption: String? = null,
+ width: Int? = null,
+ height: Int? = 300,
) = TimelineItemImageContent(
filename = filename,
fileSize = 4 * 1024 * 1024L,
@@ -38,8 +40,8 @@ fun aTimelineItemImageContent(
thumbnailSource = null,
mimeType = MimeTypes.IMAGE_JPEG,
blurhash = blurhash,
- width = null,
- height = 300,
+ width = width,
+ height = height,
thumbnailWidth = null,
thumbnailHeight = 150,
aspectRatio = aspectRatio,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt
index 4d7242ebf5..639092fc6c 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
@@ -42,6 +43,7 @@ import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
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
@@ -91,9 +93,12 @@ internal fun MessagesViewTopBar(
modifier = titleModifier
)
+ val iconModifier = Modifier.size(16.dp)
+
when (dmUserIdentityState) {
IdentityState.Verified -> {
Icon(
+ modifier = iconModifier,
imageVector = CompoundIcons.Verified(),
tint = ElementTheme.colors.iconSuccessPrimary,
contentDescription = null,
@@ -101,6 +106,7 @@ internal fun MessagesViewTopBar(
}
IdentityState.VerificationViolation -> {
Icon(
+ modifier = iconModifier,
imageVector = CompoundIcons.ErrorSolid(),
tint = ElementTheme.colors.iconCriticalPrimary,
contentDescription = null,
@@ -112,11 +118,13 @@ internal fun MessagesViewTopBar(
when (sharedHistoryIcon) {
SharedHistoryIcon.NONE -> Unit
SharedHistoryIcon.SHARED -> Icon(
+ modifier = iconModifier,
imageVector = CompoundIcons.History(),
tint = ElementTheme.colors.iconInfoPrimary,
contentDescription = stringResource(CommonStrings.common_shared_history),
)
SharedHistoryIcon.WORLD_READABLE -> Icon(
+ modifier = iconModifier,
imageVector = CompoundIcons.UserProfileSolid(),
tint = ElementTheme.colors.iconInfoPrimary,
contentDescription = stringResource(CommonStrings.common_world_readable_history),
@@ -150,7 +158,7 @@ private fun RoomAvatarAndNameRow(
)
Text(
modifier = Modifier
- .padding(horizontal = 8.dp)
+ .padding(start = 8.dp)
.semantics {
heading()
},
@@ -168,9 +176,9 @@ private fun RoomAvatarAndNameRow(
internal fun MessagesViewTopBarPreview() = ElementPreview {
@Composable
fun AMessagesViewTopBar(
- roomName: String? = "Room name",
+ roomName: String? = ROOM_NAME,
roomAvatar: AvatarData = anAvatarData(
- name = "Room name",
+ name = ROOM_NAME,
size = AvatarSize.TimelineRoom,
),
isTombstoned: Boolean = false,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/ThreadTopBar.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/ThreadTopBar.kt
index 2247566531..e73b6b19b7 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/ThreadTopBar.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/ThreadTopBar.kt
@@ -31,6 +31,7 @@ import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.preview.ROOM_NAME
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
@@ -96,9 +97,9 @@ internal fun ThreadTopBar(
internal fun ThreadTopBarPreview() = ElementPreview {
@Composable
fun AThreadTopBar(
- roomName: String? = "Room name",
+ roomName: String? = ROOM_NAME,
roomAvatarData: AvatarData = anAvatarData(
- name = "Room name",
+ name = ROOM_NAME,
size = AvatarSize.TimelineRoom,
),
isTombstoned: Boolean = false,
@@ -123,7 +124,7 @@ internal fun ThreadTopBarPreview() = ElementPreview {
HorizontalDivider()
AThreadTopBar(
roomAvatarData = anAvatarData(
- name = "Room name",
+ name = ROOM_NAME,
url = "https://some-avatar.jpg",
size = AvatarSize.TimelineRoom,
),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt
index 0506026b86..e298b3af26 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt
@@ -9,6 +9,11 @@
package io.element.android.features.messages.impl.typing
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
+import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
+import io.element.android.libraries.designsystem.preview.USER_NAME_CHARLIE
+import io.element.android.libraries.designsystem.preview.USER_NAME_DAVID
+import io.element.android.libraries.designsystem.preview.USER_NAME_EVE
import kotlinx.collections.immutable.toImmutableList
class TypingNotificationStateProvider : PreviewParameterProvider {
@@ -22,7 +27,7 @@ class TypingNotificationStateProvider : PreviewParameterProvider"Optag video"
"Vedhæftning"
"Foto- og videobibliotek"
- "Del lokation"
+ "Del placering"
"Afstemning"
"Tekstformatering"
"Beskedhistorikken er i øjeblikket ikke tilgængelig."
diff --git a/features/messages/impl/src/main/res/values-fa/translations.xml b/features/messages/impl/src/main/res/values-fa/translations.xml
index 446239c095..dcac058d2f 100644
--- a/features/messages/impl/src/main/res/values-fa/translations.xml
+++ b/features/messages/impl/src/main/res/values-fa/translations.xml
@@ -27,7 +27,7 @@
"ضبط ویدیو"
"پیوست"
"کتابخانهٔ عکس و ویدیو"
- "مکان"
+ "همرسانی مکان"
"نظرسنجی"
"قالببندی متن"
"تاریخچه پیام درحال حاضر دردسترس نیست."
diff --git a/features/messages/impl/src/main/res/values-in/translations.xml b/features/messages/impl/src/main/res/values-in/translations.xml
index 508f4d5476..7a0994f65e 100644
--- a/features/messages/impl/src/main/res/values-in/translations.xml
+++ b/features/messages/impl/src/main/res/values-in/translations.xml
@@ -34,7 +34,7 @@
"Rekam video"
"Lampiran"
"Pustaka Foto & Video"
- "Lokasi"
+ "Membagi Lokasi"
"Jajak pendapat"
"Pemformatan Teks"
"Riwayat pesan saat ini tidak tersedia di ruangan ini"
diff --git a/features/messages/impl/src/main/res/values-ja/translations.xml b/features/messages/impl/src/main/res/values-ja/translations.xml
index 4fd18e8db5..037c0f7d67 100644
--- a/features/messages/impl/src/main/res/values-ja/translations.xml
+++ b/features/messages/impl/src/main/res/values-ja/translations.xml
@@ -34,7 +34,7 @@
"写真を撮影"
"動画を撮影"
"添付ファイル"
- "アルバムの写真・動画"
+ "アルバムの写真と動画"
"場所を共有"
"投票"
"書式設定"
diff --git a/features/messages/impl/src/main/res/values-pt/translations.xml b/features/messages/impl/src/main/res/values-pt/translations.xml
index 054d2a1759..2fb5173169 100644
--- a/features/messages/impl/src/main/res/values-pt/translations.xml
+++ b/features/messages/impl/src/main/res/values-pt/translations.xml
@@ -35,7 +35,7 @@
"Gravar vídeo"
"Anexo"
"Biblioteca de fotos e vídeos"
- "Localização"
+ "Partilhar localização"
"Sondagem"
"Formatação de texto"
"De momento, o histórico de mensagens está indisponível."
diff --git a/features/messages/impl/src/main/res/values-ro/translations.xml b/features/messages/impl/src/main/res/values-ro/translations.xml
index ff380874ea..67b72dc2a8 100644
--- a/features/messages/impl/src/main/res/values-ro/translations.xml
+++ b/features/messages/impl/src/main/res/values-ro/translations.xml
@@ -35,7 +35,7 @@
"Înregistrați un videoclip"
"Atașament"
"Bibliotecă foto și video"
- "Locație"
+ "Partajați locația"
"Sondaj"
"Formatarea textului"
"Mesajele anterioare nu sunt momentan disponibile în această cameră"
diff --git a/features/messages/impl/src/main/res/values-zh/translations.xml b/features/messages/impl/src/main/res/values-zh/translations.xml
index 2a6b9bf78d..fc576aaf11 100644
--- a/features/messages/impl/src/main/res/values-zh/translations.xml
+++ b/features/messages/impl/src/main/res/values-zh/translations.xml
@@ -7,13 +7,13 @@
"由未知或已删除的设备加密。"
"由未经其所有者验证的设备加密。"
"由未经验证的用户加密。"
- "活动"
+ "节假日"
"旗帜"
- "食物和饮料"
+ "饮食"
"动物和自然"
- "物品"
+ "日常物品"
"表情和人物"
- "旅行和地点"
+ "文旅景点"
"最近的 Emoji"
"符号"
"使用旧版应用程序的用户可能无法看到字幕。"
@@ -21,15 +21,15 @@
"无法上传该文件。"
"处理要上传的媒体失败,请重试。"
"上传媒体失败,请重试。"
- "允许的最大文件大小为%1$s 。"
+ "允许的最大文件大小为 %1$s。"
"文件太大,无法上传"
- "第%1$d/%2$d项"
+ "第 %1$d 个项目,共 %2$d 个"
"优化图像质量"
"处理中…"
"屏蔽用户"
- "请确认是否要隐藏该用户当前和未来的所有信息"
- "此消息将举报给您的服务器管理员。他们无法读取任何加密消息。"
- "举报此内容的原因"
+ "请确认是否要隐藏该用户当前和未来的所有消息"
+ "此消息将举报给服务器管理员。他们无法读取任何加密消息。"
+ "举报此内容的理由"
"相机"
"拍摄照片"
"录制视频"
@@ -39,40 +39,40 @@
"投票"
"文本格式化"
"消息历史记录当前不可用。"
- "此聊天室无法查看消息历史记录。请验证此设备以查看之。"
- "您想邀请他们回来吗?"
- "此聊天室中只有您一个人"
- "通知整个聊天室"
+ "消息历史在此房间不可用。请验证此设备以查看。"
+ "你想邀请他们回来吗?"
+ "此聊天中只有你一人"
+ "通知整个房间"
"所有人"
"再次发送"
"消息发送失败"
- "添加表情符号"
- "这是 %1$s 聊天室的开始。"
+ "添加反应"
+ "这是房间 %1$s 的开头。"
"这是本对话的开始。"
- "不支持的呼叫。询问呼叫者是否可以使用新的 Element X 应用程序。"
+ "不受支持的通话。询问呼叫方是否可以使用新的 Element X app。"
"折叠"
"消息已复制"
- "您无权在此聊天室发言"
+ "你无权在此房间发言"
- - "%1$d 个成员添加表情符号 %2$s"
+ - "%1$d 个成员使用 %2$s 反应"
- - "您与 %1$d 个成员添加表情符号 %2$s"
+ - "你与其他 %1$d 个成员使用 %2$s 反应"
- "您添加了表情符号%1$s"
+ "你使用 %1$s 反应"
"折叠"
"展开"
"显示反应摘要"
"新消息"
- - "%1$d 个聊天室变化"
+ - "%1$d 个房间变化"
- "跳转至新房间"
+ "跳转到新房间"
"本房间已被替换,现已失效"
"查看历史消息"
- "该聊天室是其他聊天室的延续"
+ "此房间是另一房间的延续"
- - "%1$s,%2$s 和其他 %3$d 个人"
+ - "%1$s,%2$s 及其他 %3$d 人"
- "%1$s 正在输入"
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
index 6e12c607d8..50bacb005f 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
@@ -1228,9 +1228,6 @@ class MessagesPresenterTest {
initialRoomInfo = aRoomInfo(isEncrypted = true, historyVisibility = RoomHistoryVisibility.Shared),
),
),
- featureFlagService = FakeFeatureFlagService(
- initialState = mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true)
- )
)
presenter.testWithLifecycleOwner {
awaitItem()
@@ -1249,9 +1246,6 @@ class MessagesPresenterTest {
initialRoomInfo = aRoomInfo(isEncrypted = true, historyVisibility = RoomHistoryVisibility.WorldReadable),
),
),
- featureFlagService = FakeFeatureFlagService(
- initialState = mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true)
- )
)
presenter.testWithLifecycleOwner {
awaitItem()
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt
index 62b9eac68d..70ef70325e 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt
@@ -6,13 +6,15 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl
import androidx.activity.ComponentActivity
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onAllNodesWithTag
@@ -25,6 +27,7 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.compose.ui.text.AnnotatedString
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.emojibasebindings.Emoji
@@ -78,82 +81,78 @@ import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.setSafeContent
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import kotlin.time.Duration.Companion.milliseconds
@RunWith(AndroidJUnit4::class)
class MessagesViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back invoke expected callback`() {
+ fun `clicking on back invoke expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aMessagesState(
eventSink = eventsRecorder
)
ensureCalledOnce { callback ->
- rule.setMessagesView(
+ setMessagesView(
state = state,
onBackClick = callback,
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `clicking on room name invoke expected callback`() {
+ fun `clicking on room name invoke expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aMessagesState(
eventSink = eventsRecorder
)
ensureCalledOnce { callback ->
- rule.setMessagesView(
+ setMessagesView(
state = state,
onRoomDetailsClick = callback,
)
- rule.onNodeWithText(state.roomName.orEmpty(), useUnmergedTree = true).performClick()
+ onNodeWithText(state.roomName.orEmpty(), useUnmergedTree = true).performClick()
}
}
@Test
- fun `clicking on join call invoke expected callback`() {
+ fun `clicking on join call invoke expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aMessagesState(
eventSink = eventsRecorder
)
ensureCalledOnceWithParam(false) { callback ->
- rule.setMessagesView(
+ setMessagesView(
state = state,
onJoinCallClick = callback,
)
- val joinCallContentDescription = rule.activity.getString(CommonStrings.a11y_start_call)
- rule.onNodeWithContentDescription(joinCallContentDescription).performClick()
+ val joinCallContentDescription = activity!!.getString(CommonStrings.a11y_start_call)
+ onNodeWithContentDescription(joinCallContentDescription).performClick()
}
}
@Test
- fun `clicking on join voice call invoke expected callback`() {
+ fun `clicking on join voice call invoke expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aMessagesState(
eventSink = eventsRecorder,
roomCallState = aStandByCallState(isDM = true)
)
ensureCalledOnceWithParam(true) { callback ->
- rule.setMessagesView(
+ setMessagesView(
state = state,
onJoinCallClick = callback,
)
- val joinVoiceCallContentDescription = rule.activity.getString(CommonStrings.a11y_start_voice_call)
- rule.onNodeWithContentDescription(joinVoiceCallContentDescription).performClick()
+ val joinVoiceCallContentDescription = activity!!.getString(CommonStrings.a11y_start_voice_call)
+ onNodeWithContentDescription(joinVoiceCallContentDescription).performClick()
}
}
@Test
- fun `clicking on an Event invoke expected callback`() {
+ fun `clicking on an Event invoke expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aMessagesState(
timelineState = aTimelineState(
@@ -167,12 +166,12 @@ class MessagesViewTest {
expectedParam2 = timelineItem,
result = true,
)
- rule.setMessagesView(
+ setMessagesView(
state = state,
onEventClick = callback,
)
// Cannot perform click on "Text", it's not detected. Use tag instead
- rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performClick()
+ onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performClick()
callback.assertSuccess()
}
@@ -202,7 +201,7 @@ class MessagesViewTest {
userHasPermissionToRedactOther: Boolean = false,
userHasPermissionToSendReaction: Boolean = false,
userCanPinEvent: Boolean = false,
- ) {
+ ) = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
actionListState = anActionListState(
@@ -220,11 +219,11 @@ class MessagesViewTest {
),
)
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
- rule.setMessagesView(
+ setMessagesView(
state = state,
)
// Cannot perform click on "Text", it's not detected. Use tag instead
- rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() }
+ onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() }
eventsRecorder.assertSingle(
ActionListEvent.ComputeForMessage(
event = timelineItem,
@@ -235,7 +234,7 @@ class MessagesViewTest {
@Test
@Config(qualifiers = "h1024dp")
- fun `clicking on a read receipt list emits the expected Event`() {
+ fun `clicking on a read receipt list emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
timelineState = aTimelineState(
@@ -255,10 +254,10 @@ class MessagesViewTest {
),
)
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
- rule.setMessagesView(
+ setMessagesView(
state = state,
)
- rule.onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick()
+ onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick()
eventsRecorder.assertSingle(ReadReceiptBottomSheetEvent.EventSelected(timelineItem))
}
@@ -272,7 +271,7 @@ class MessagesViewTest {
swipeTest(userHasPermissionToSendMessage = false)
}
- private fun swipeTest(userHasPermissionToSendMessage: Boolean) {
+ private fun swipeTest(userHasPermissionToSendMessage: Boolean) = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true)
val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false)
@@ -285,10 +284,10 @@ class MessagesViewTest {
),
eventSink = eventsRecorder,
)
- rule.setMessagesView(
+ setMessagesView(
state = state,
)
- rule.onAllNodesWithTag(TestTags.messageBubble.value).apply {
+ onAllNodesWithTag(TestTags.messageBubble.value).apply {
onFirst().performTouchInput { swipeRight(endX = 200f) }
onLast().performTouchInput { swipeRight(endX = 200f) }
}
@@ -300,7 +299,7 @@ class MessagesViewTest {
}
@Test
- fun `clicking on send location invoke expected callback`() {
+ fun `clicking on send location invoke expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aMessagesState(
composerState = aMessageComposerState(
@@ -309,16 +308,16 @@ class MessagesViewTest {
eventSink = eventsRecorder
)
ensureCalledOnce { callback ->
- rule.setMessagesView(
+ setMessagesView(
state = state,
onSendLocationClick = callback,
)
- rule.clickOn(R.string.screen_room_attachment_source_location)
+ clickOn(R.string.screen_room_attachment_source_location)
}
}
@Test
- fun `clicking on create poll invoke expected callback`() {
+ fun `clicking on create poll invoke expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aMessagesState(
composerState = aMessageComposerState(
@@ -327,25 +326,25 @@ class MessagesViewTest {
eventSink = eventsRecorder
)
ensureCalledOnce { callback ->
- rule.setMessagesView(
+ setMessagesView(
state = state,
onCreatePollClick = callback,
)
// Then click on the poll action
- rule.clickOn(R.string.screen_room_attachment_source_poll)
+ clickOn(R.string.screen_room_attachment_source_poll)
}
}
@Test
@Config(qualifiers = "h1024dp")
- fun `clicking on the avatar of the sender of an Event emits the expected event`() {
+ fun `clicking on the avatar of the sender of an Event emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
eventSink = eventsRecorder
)
val timelineEvent = state.timelineState.timelineItems.filterIsInstance().first()
- rule.setMessagesView(state = state)
- rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
+ setMessagesView(state = state)
+ onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
eventsRecorder.assertSingle(
MessagesEvent.OnUserClicked(
MatrixUser(
@@ -359,12 +358,12 @@ class MessagesViewTest {
@Test
@Config(qualifiers = "h1024dp")
- fun `clicking on the display name of the sender of an Event emits expected event`() {
+ fun `clicking on the display name of the sender of an Event emits expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(eventSink = eventsRecorder)
val timelineEvent = state.timelineState.timelineItems.filterIsInstance().first()
- rule.setMessagesView(state = state)
- rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
+ setMessagesView(state = state)
+ onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
eventsRecorder.assertSingle(
MessagesEvent.OnUserClicked(
MatrixUser(
@@ -377,7 +376,7 @@ class MessagesViewTest {
}
@Test
- fun `selecting a action on a message emits the expected Event`() {
+ fun `selecting a action on a message emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
eventSink = eventsRecorder
@@ -395,17 +394,17 @@ class MessagesViewTest {
)
),
)
- rule.setMessagesView(
+ setMessagesView(
state = stateWithMessageAction,
)
- rule.clickOn(CommonStrings.action_edit)
+ clickOn(CommonStrings.action_edit)
// Give time for the close animation to complete
- rule.mainClock.advanceTimeBy(milliseconds = 1_000)
+ mainClock.advanceTimeBy(milliseconds = 1_000)
eventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Edit, timelineItem))
}
@Test
- fun `clicking on a reaction emits the expected Event`() {
+ fun `clicking on a reaction emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
timelineState = aTimelineState(
@@ -414,10 +413,10 @@ class MessagesViewTest {
eventSink = eventsRecorder,
)
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
- rule.setMessagesView(
+ setMessagesView(
state = state,
)
- rule.onAllNodesWithText(
+ onAllNodesWithText(
text = "👍️",
useUnmergedTree = true,
).onFirst().performClick()
@@ -425,7 +424,7 @@ class MessagesViewTest {
}
@Test
- fun `long clicking on a reaction emits the expected Event`() {
+ fun `long clicking on a reaction emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
timelineState = aTimelineState(
@@ -437,10 +436,10 @@ class MessagesViewTest {
),
)
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
- rule.setMessagesView(
+ setMessagesView(
state = state,
)
- rule.onAllNodesWithText(
+ onAllNodesWithText(
text = "👍️",
useUnmergedTree = true,
).onFirst().performTouchInput { longClick() }
@@ -448,7 +447,7 @@ class MessagesViewTest {
}
@Test
- fun `clicking on more reaction emits the expected Event`() {
+ fun `clicking on more reaction emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
timelineState = aTimelineState(
@@ -459,16 +458,16 @@ class MessagesViewTest {
),
)
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
- rule.setMessagesView(
+ setMessagesView(
state = state,
)
- val moreReactionContentDescription = rule.activity.getString(R.string.screen_room_timeline_add_reaction)
- rule.onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick()
+ val moreReactionContentDescription = activity!!.getString(R.string.screen_room_timeline_add_reaction)
+ onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick()
eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem))
}
@Test
- fun `clicking on more reaction from action list emits the expected Event`() {
+ fun `clicking on more reaction from action list emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
timelineState = aTimelineState(
@@ -491,18 +490,18 @@ class MessagesViewTest {
eventSink = eventsRecorder
),
)
- rule.setMessagesView(
+ setMessagesView(
state = stateWithActionListState,
)
- val moreReactionContentDescription = rule.activity.getString(CommonStrings.a11y_react_with_other_emojis)
- rule.onNodeWithContentDescription(moreReactionContentDescription).performClick()
+ val moreReactionContentDescription = activity!!.getString(CommonStrings.a11y_react_with_other_emojis)
+ onNodeWithContentDescription(moreReactionContentDescription).performClick()
// Give time for the close animation to complete
- rule.mainClock.advanceTimeBy(milliseconds = 1_000)
+ mainClock.advanceTimeBy(milliseconds = 1_000)
eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem))
}
@Test
- fun `clicking on verified user send failure from action list emits the expected Event`() {
+ fun `clicking on verified user send failure from action list emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState()
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
@@ -519,21 +518,21 @@ class MessagesViewTest {
),
timelineState = aTimelineState(eventSink = eventsRecorder)
)
- rule.setMessagesView(
+ setMessagesView(
state = stateWithActionListState,
)
// Clear initial 'LoadMore' event emitted when setting the state
eventsRecorder.clear()
- val verifiedUserSendFailure = rule.activity.getString(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, "Alice")
- rule.onNodeWithText(verifiedUserSendFailure).performClick()
+ val verifiedUserSendFailure = activity!!.getString(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, "Alice")
+ onNodeWithText(verifiedUserSendFailure).performClick()
// Give time for the close animation to complete
- rule.mainClock.advanceTimeBy(milliseconds = 1_000)
+ mainClock.advanceTimeBy(milliseconds = 1_000)
eventsRecorder.assertSingle(TimelineEvent.ComputeVerifiedUserSendFailure(timelineItem))
}
@Test
- fun `clicking on a custom emoji emits the expected Events`() {
+ fun `clicking on a custom emoji emits the expected Events`() = runAndroidComposeUiTest {
val aUnicode = "🙈"
val customReactionStateEventsRecorder = EventsRecorder()
val eventsRecorder = EventsRecorder()
@@ -563,18 +562,18 @@ class MessagesViewTest {
eventSink = customReactionStateEventsRecorder
),
)
- rule.setMessagesView(
+ setMessagesView(
state = stateWithCustomReactionState,
)
- rule.onNodeWithText(aUnicode, useUnmergedTree = true).performClick()
+ onNodeWithText(aUnicode, useUnmergedTree = true).performClick()
// Give time for the close animation to complete
- rule.mainClock.advanceTimeBy(milliseconds = 1_000)
+ mainClock.advanceTimeBy(milliseconds = 1_000)
customReactionStateEventsRecorder.assertSingle(CustomReactionEvent.DismissCustomReactionSheet)
eventsRecorder.assertSingle(MessagesEvent.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId))
}
@Test
- fun `clicking on pinned messages banner emits the expected Event`() {
+ fun `clicking on pinned messages banner emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aMessagesState(
timelineState = aTimelineState(eventSink = eventsRecorder),
@@ -587,16 +586,16 @@ class MessagesViewTest {
),
),
)
- rule.setMessagesView(state = state)
+ setMessagesView(state = state)
// Clear initial 'LoadMore' event emitted when setting the state
eventsRecorder.clear()
- rule.onNodeWithText("This is a pinned message").performClick()
+ onNodeWithText("This is a pinned message").performClick()
eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
}
@Test
- fun `clicking on successor room button emits expected event`() {
+ fun `clicking on successor room button emits expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val successorRoomId = RoomId("!successor:server.org")
val state = aMessagesState(
@@ -606,18 +605,18 @@ class MessagesViewTest {
),
timelineState = aTimelineState(eventSink = eventsRecorder)
)
- rule.setMessagesView(state = state)
+ setMessagesView(state = state)
// Clear initial 'LoadMore' event emitted when setting the state
eventsRecorder.clear()
- val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
+ val text = activity!!.getString(R.string.screen_room_timeline_tombstoned_room_action)
// The bottomsheet subcompose seems to make the node to appear twice
- rule.onAllNodesWithText(text).onFirst().performClick()
+ onAllNodesWithText(text).onFirst().performClick()
eventsRecorder.assertSingle(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(successorRoomId))
}
@Test
- fun `clicking on threads list button calls the expected function`() {
+ fun `clicking on threads list button calls the expected function`() = runAndroidComposeUiTest {
val state = aMessagesState(
threads = MessagesState.Threads(
hasThreads = true,
@@ -625,28 +624,28 @@ class MessagesViewTest {
)
)
val onThreadsListClicked = lambdaRecorder {}
- rule.setMessagesView(
+ setMessagesView(
state = state,
onThreadsListClicked = onThreadsListClicked,
)
- rule.onNodeWithContentDescription("Threads").performClick()
+ onNodeWithContentDescription("Threads").performClick()
onThreadsListClicked.assertions().isCalledOnce()
}
@Test
- fun `no banner shown when there is no successor room`() {
+ fun `no banner shown when there is no successor room`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aMessagesState(
successorRoom = null,
eventSink = eventsRecorder
)
- rule.setMessagesView(state = state)
- rule.assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_message)
- rule.assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_action)
+ setMessagesView(state = state)
+ assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_message)
+ assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_action)
}
}
-private fun AndroidComposeTestRule.setMessagesView(
+private fun AndroidComposeUiTest.setMessagesView(
state: MessagesState,
onBackClick: () -> Unit = EnsureNeverCalled(),
onRoomDetailsClick: () -> Unit = EnsureNeverCalled(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt
index 24779ba78a..0ee342513a 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt
@@ -6,12 +6,15 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.crypto.identity
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.matrix.api.core.UserId
@@ -21,19 +24,15 @@ import io.element.android.libraries.matrix.ui.room.RoomMemberIdentityStateChange
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class IdentityChangeStateViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `show and resolve pin violation`() {
+ fun `show and resolve pin violation`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setIdentityChangeStateView(
+ setIdentityChangeStateView(
state = anIdentityChangeState(
listOf(
RoomMemberIdentityStateChange(
@@ -45,18 +44,18 @@ class IdentityChangeStateViewTest {
),
)
- rule.onNodeWithText("identity was reset", substring = true).assertExists("should display pin violation warning")
- rule.onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid")
- rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname")
+ onNodeWithText("identity was reset", substring = true).assertExists("should display pin violation warning")
+ onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid")
+ onNodeWithText("Alice", substring = true).assertExists("should display user displayname")
- rule.clickOn(res = CommonStrings.action_dismiss)
+ clickOn(res = CommonStrings.action_dismiss)
eventsRecorder.assertSingle(IdentityChangeEvent.PinIdentity(UserId("@alice:localhost")))
}
@Test
- fun `show and resolve verification violation`() {
+ fun `show and resolve verification violation`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setIdentityChangeStateView(
+ setIdentityChangeStateView(
state = anIdentityChangeState(
listOf(
RoomMemberIdentityStateChange(
@@ -68,17 +67,17 @@ class IdentityChangeStateViewTest {
),
)
- rule.onNodeWithText("identity was reset", substring = true).assertExists("should display verification violation warning")
- rule.onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid")
- rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname")
+ onNodeWithText("identity was reset", substring = true).assertExists("should display verification violation warning")
+ onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid")
+ onNodeWithText("Alice", substring = true).assertExists("should display user displayname")
- rule.clickOn(res = CommonStrings.crypto_identity_change_withdraw_verification_action)
+ clickOn(res = CommonStrings.crypto_identity_change_withdraw_verification_action)
eventsRecorder.assertSingle(IdentityChangeEvent.WithdrawVerification(UserId("@alice:localhost")))
}
@Test
- fun `Should not show any banner if no violations`() {
- rule.setIdentityChangeStateView(
+ fun `Should not show any banner if no violations`() = runAndroidComposeUiTest {
+ setIdentityChangeStateView(
state = anIdentityChangeState(
listOf(
RoomMemberIdentityStateChange(
@@ -93,10 +92,10 @@ class IdentityChangeStateViewTest {
),
)
- rule.onNodeWithText("identity was reset", substring = true).assertDoesNotExist()
+ onNodeWithText("identity was reset", substring = true).assertDoesNotExist()
}
- private fun AndroidComposeTestRule.setIdentityChangeStateView(
+ private fun AndroidComposeUiTest.setIdentityChangeStateView(
state: IdentityChangeState,
) {
setContent {
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt
index 02767fbeb9..07a0fd5f94 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt
@@ -6,54 +6,53 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.crypto.sendfailure.resolve
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.setSafeContent
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ResolveVerifiedUserSendFailureViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on resolve and resend emit the expected event`() {
+ fun `clicking on resolve and resend emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setResolveVerifiedUserSendFailureView(
+ setResolveVerifiedUserSendFailureView(
state = aResolveVerifiedUserSendFailureState(
verifiedUserSendFailure = aChangedIdentitySendFailure(),
eventSink = eventsRecorder,
),
)
- rule.clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title)
+ clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title)
eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
}
@Test
- fun `clicking on retry emit the expected event`() {
+ fun `clicking on retry emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setResolveVerifiedUserSendFailureView(
+ setResolveVerifiedUserSendFailureView(
state = aResolveVerifiedUserSendFailureState(
verifiedUserSendFailure = aChangedIdentitySendFailure(),
eventSink = eventsRecorder,
),
)
- rule.clickOn(res = CommonStrings.action_retry)
+ clickOn(res = CommonStrings.action_retry)
eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.Retry)
}
- private fun AndroidComposeTestRule.setResolveVerifiedUserSendFailureView(
+ private fun AndroidComposeUiTest.setResolveVerifiedUserSendFailureView(
state: ResolveVerifiedUserSendFailureState,
) {
setSafeContent {
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt
index e198ea9043..b656430466 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.link
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.ui.strings.CommonStrings
@@ -19,51 +22,46 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.wysiwyg.link.Link
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LinkViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on cancel emits the expected event`() {
+ fun `clicking on cancel emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLinkView(
+ setLinkView(
aLinkState(
linkClick = ConfirmingLinkClick(aLink),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_cancel)
+ clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(
LinkEvent.Cancel
)
}
@Test
- fun `clicking on continue emits the expected event`() {
+ fun `clicking on continue emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setLinkView(
+ setLinkView(
aLinkState(
linkClick = ConfirmingLinkClick(aLink),
eventSink = eventsRecorder,
),
)
- rule.clickOn(CommonStrings.action_continue)
+ clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(
LinkEvent.Confirm
)
}
@Test
- fun `success state invokes the callback and emits the expected event`() {
+ fun `success state invokes the callback and emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
ensureCalledOnceWithParam(aLink) { callback ->
- rule.setLinkView(
+ setLinkView(
aLinkState(
linkClick = AsyncAction.Success(aLink),
eventSink = eventsRecorder,
@@ -77,7 +75,7 @@ class LinkViewTest {
}
}
-private fun AndroidComposeTestRule.setLinkView(
+private fun AndroidComposeUiTest.setLinkView(
state: LinkState,
onLinkValid: (Link) -> Unit = EnsureNeverCalledWithParam(),
) {
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt
index 2c33e348c0..546731ff87 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt
@@ -6,13 +6,16 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.pinned.banner
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.ui.strings.CommonStrings
@@ -22,49 +25,45 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PinnedMessagesBannerViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on the banner invoke expected callback`() {
+ fun `clicking on the banner invoke expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val state = aLoadedPinnedMessagesBannerState(
eventSink = eventsRecorder
)
val pinnedEventId = state.currentPinnedMessage.eventId
ensureCalledOnceWithParam(pinnedEventId) { callback ->
- rule.setPinnedMessagesBannerView(
+ setPinnedMessagesBannerView(
state = state,
onClick = callback
)
- rule.onRoot().performClick()
+ onRoot().performClick()
eventsRecorder.assertSingle(PinnedMessagesBannerEvent.MoveToNextPinned)
}
}
@Test
- fun `clicking on view all emit the expected event`() {
+ fun `clicking on view all emit the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = true)
val state = aLoadedPinnedMessagesBannerState(
eventSink = eventsRecorder
)
ensureCalledOnce { callback ->
- rule.setPinnedMessagesBannerView(
+ setPinnedMessagesBannerView(
state = state,
onViewAllClick = callback
)
- rule.clickOn(CommonStrings.screen_room_pinned_banner_view_all_button_title)
+ clickOn(CommonStrings.screen_room_pinned_banner_view_all_button_title)
}
}
}
-private fun AndroidComposeTestRule.setPinnedMessagesBannerView(
+private fun AndroidComposeUiTest.setPinnedMessagesBannerView(
state: PinnedMessagesBannerState,
onClick: (EventId) -> Unit = EnsureNeverCalledWithParam(),
onViewAllClick: () -> Unit = EnsureNeverCalled(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt
index 41671b71c1..9c10abb631 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt
@@ -6,16 +6,19 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.pinned.list
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.messages.impl.actionlist.ActionListEvent
import io.element.android.features.messages.impl.actionlist.anActionListState
@@ -31,33 +34,28 @@ import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.setSafeContent
import io.element.android.wysiwyg.link.Link
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PinnedMessagesListViewTest {
- @get:Rule
- val rule = createAndroidComposeRule()
-
@Test
- fun `clicking on back calls the expected callback`() {
+ fun `clicking on back calls the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val state = aLoadedPinnedMessagesListState(
eventSink = eventsRecorder
)
ensureCalledOnce { callback ->
- rule.setPinnedMessagesListView(
+ setPinnedMessagesListView(
state = state,
onBackClick = callback
)
- rule.pressBack()
+ pressBack()
}
}
@Test
- fun `click on an event calls the expected callback`() {
+ fun `click on an event calls the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = false)
val content = aTimelineItemFileContent()
val state = aLoadedPinnedMessagesListState(
@@ -67,16 +65,16 @@ class PinnedMessagesListViewTest {
val event = state.timelineItems.first() as TimelineItem.Event
ensureCalledOnceWithParam(event) { callback ->
- rule.setPinnedMessagesListView(
+ setPinnedMessagesListView(
state = state,
onEventClick = callback
)
- rule.onAllNodesWithText(content.filename).onFirst().performClick()
+ onAllNodesWithText(content.filename).onFirst().performClick()
}
}
@Test
- fun `long click on an event emits the expected event`() {
+ fun `long click on an event emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder(expectEvents = true)
val content = aTimelineItemFileContent()
val state = aLoadedPinnedMessagesListState(
@@ -84,10 +82,10 @@ class PinnedMessagesListViewTest {
actionListState = anActionListState(eventSink = eventsRecorder)
)
- rule.setPinnedMessagesListView(
+ setPinnedMessagesListView(
state = state,
)
- rule.onAllNodesWithText(content.filename).onFirst()
+ onAllNodesWithText(content.filename).onFirst()
.performTouchInput {
longClick()
}
@@ -96,7 +94,7 @@ class PinnedMessagesListViewTest {
}
}
-private fun AndroidComposeTestRule.setPinnedMessagesListView(
+private fun AndroidComposeUiTest.setPinnedMessagesListView(
state: PinnedMessagesListState,
onBackClick: () -> Unit = EnsureNeverCalled(),
onEventClick: (event: TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt
index 315d9c459c..9e98f0fa49 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt
@@ -6,11 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.timeline
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runComposeUiTest
import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter
import io.element.android.libraries.core.extensions.runCatchingExceptions
@@ -18,15 +21,12 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class DefaultHtmlConverterProviderTest {
- @get:Rule val composeTestRule = createComposeRule()
-
private val provider = DefaultHtmlConverterProvider(
mentionSpanProvider = MentionSpanProvider(
permalinkParser = FakePermalinkParser(),
@@ -43,8 +43,8 @@ class DefaultHtmlConverterProviderTest {
}
@Test
- fun `calling provide after calling Update first should return an HtmlConverter`() {
- composeTestRule.setContent {
+ fun `calling provide after calling Update first should return an HtmlConverter`() = runComposeUiTest {
+ setContent {
CompositionLocalProvider(LocalInspectionMode provides true) {
provider.Update()
}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt
index 3a0b0e1224..2138d4ced2 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt
@@ -6,15 +6,18 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.timeline
import androidx.activity.ComponentActivity
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToIndex
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.messages.impl.timeline.components.MessageShieldData
import io.element.android.features.messages.impl.timeline.components.aCriticalShield
@@ -39,19 +42,15 @@ import io.element.android.wysiwyg.link.Link
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import org.junit.Ignore
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TimelineViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `reaching the end of the timeline with more events to load emits a LoadMore event`() {
+ fun `reaching the end of the timeline with more events to load emits a LoadMore event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setTimelineView(
+ setTimelineView(
state = aTimelineState(
timelineItems = persistentListOf(
TimelineItem.Virtual(
@@ -66,9 +65,9 @@ class TimelineViewTest {
}
@Test
- fun `reaching the end of the timeline does not send a LoadMore event`() {
+ fun `reaching the end of the timeline does not send a LoadMore event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setTimelineView(
+ setTimelineView(
state = aTimelineState(
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
eventSink = eventsRecorder,
@@ -78,9 +77,9 @@ class TimelineViewTest {
}
@Test
- fun `scroll to bottom on live timeline does not emit the Event`() {
+ fun `scroll to bottom on live timeline does not emit the Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setTimelineView(
+ setTimelineView(
state = aTimelineState(
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
isLive = true,
@@ -92,14 +91,14 @@ class TimelineViewTest {
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
eventsRecorder.clear()
- val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
- rule.onNodeWithContentDescription(contentDescription).performClick()
+ val contentDescription = activity!!.getString(CommonStrings.a11y_jump_to_bottom)
+ onNodeWithContentDescription(contentDescription).performClick()
}
@Test
- fun `scroll to bottom on detached timeline emits the expected Event`() {
+ fun `scroll to bottom on detached timeline emits the expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setTimelineView(
+ setTimelineView(
state = aTimelineState(
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
isLive = false,
@@ -110,15 +109,15 @@ class TimelineViewTest {
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
eventsRecorder.clear()
- val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
- rule.onNodeWithContentDescription(contentDescription).performClick()
+ val contentDescription = activity!!.getString(CommonStrings.a11y_jump_to_bottom)
+ onNodeWithContentDescription(contentDescription).performClick()
eventsRecorder.assertSingle(TimelineEvent.JumpToLive)
}
@Test
- fun `an empty timeline triggers a prefetch`() {
+ fun `an empty timeline triggers a prefetch`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setTimelineView(
+ setTimelineView(
state = aTimelineState(
timelineItems = persistentListOf(),
eventSink = eventsRecorder,
@@ -129,9 +128,9 @@ class TimelineViewTest {
}
@Test
- fun `show shield dialog`() {
+ fun `show shield dialog`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setTimelineView(
+ setTimelineView(
state = aTimelineState(
timelineItems = persistentListOf(
aTimelineItemEvent(
@@ -143,8 +142,8 @@ class TimelineViewTest {
eventSink = eventsRecorder,
),
)
- val contentDescription = rule.activity.getString(CommonStrings.a11y_encryption_details)
- rule.onNodeWithContentDescription(contentDescription).performClick()
+ val contentDescription = activity!!.getString(CommonStrings.a11y_encryption_details)
+ onNodeWithContentDescription(contentDescription).performClick()
eventsRecorder.assertList(
listOf(
TimelineEvent.OnScrollFinished(0),
@@ -154,9 +153,9 @@ class TimelineViewTest {
}
@Test
- fun `hide shield dialog`() {
+ fun `hide shield dialog`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
- rule.setTimelineView(
+ setTimelineView(
state = aTimelineState(
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
isLive = false,
@@ -167,16 +166,16 @@ class TimelineViewTest {
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
eventsRecorder.clear()
- rule.clickOn(CommonStrings.action_ok)
+ clickOn(CommonStrings.action_ok)
eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog)
}
@Ignore(
"performScrollToIndex in compose tests no longer sets LazyListState.isScrollInProgress to true, so the LoadMore event is not emitted." +
- "This needs to be reworked to use a different approach to check the LoadMore event was emitted."
+ "This needs to be reworked to use a different approach to check the LoadMore event was emitted."
)
@Test
- fun `scrolling near to the start of the loaded items triggers a pre-fetch`() {
+ fun `scrolling near to the start of the loaded items triggers a pre-fetch`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val items = List(200) {
aTimelineItemEvent(
@@ -185,7 +184,7 @@ class TimelineViewTest {
)
}.toImmutableList()
- rule.setTimelineView(
+ setTimelineView(
state = aTimelineState(
timelineItems = items,
eventSink = eventsRecorder,
@@ -194,9 +193,9 @@ class TimelineViewTest {
),
)
- rule.onNodeWithTag("timeline").performScrollToIndex(180)
+ onNodeWithTag("timeline").performScrollToIndex(180)
- rule.mainClock.advanceTimeBy(1000)
+ mainClock.advanceTimeBy(1000)
eventsRecorder.assertList(
listOf(
@@ -207,7 +206,7 @@ class TimelineViewTest {
}
}
-private fun AndroidComposeTestRule.setTimelineView(
+private fun AndroidComposeUiTest.setTimelineView(
state: TimelineState,
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
onUserDataClick: (MatrixUser) -> Unit = EnsureNeverCalledWithParam(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt
index 64b5216d2e..40671e4bf8 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt
@@ -6,12 +6,15 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.timeline.components.event
import androidx.activity.ComponentActivity
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
@@ -20,14 +23,11 @@ import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.pressTag
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TimelineItemPollViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
fun `answering a poll with first answer should emit a PollAnswerSelected event`() {
testAnswer(answerIndex = 0)
@@ -38,17 +38,17 @@ class TimelineItemPollViewTest {
testAnswer(answerIndex = 1)
}
- private fun testAnswer(answerIndex: Int) {
+ private fun testAnswer(answerIndex: Int) = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val content = aTimelineItemPollContent()
- rule.setContent {
+ setContent {
TimelineItemPollView(
content = content,
eventSink = eventsRecorder
)
}
val answer = content.answerItems[answerIndex].answer
- rule.onNode(
+ onNode(
matcher = hasText(answer.text),
useUnmergedTree = true,
).performClick()
@@ -56,38 +56,38 @@ class TimelineItemPollViewTest {
}
@Test
- fun `editing a poll should emit a PollEditClicked event`() {
+ fun `editing a poll should emit a PollEditClicked event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val content = aTimelineItemPollContent(
isMine = true,
isEditable = true,
)
- rule.setContent {
+ setContent {
TimelineItemPollView(
content = content,
eventSink = eventsRecorder
)
}
- rule.clickOn(CommonStrings.action_edit_poll)
+ clickOn(CommonStrings.action_edit_poll)
eventsRecorder.assertSingle(TimelineEvent.EditPoll(content.eventId!!))
}
@Test
- fun `closing a poll should emit a PollEndClicked event`() {
+ fun `closing a poll should emit a PollEndClicked event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder()
val content = aTimelineItemPollContent(
isMine = true,
)
- rule.setContent {
+ setContent {
TimelineItemPollView(
content = content,
eventSink = eventsRecorder
)
}
- rule.clickOn(CommonStrings.action_end_poll)
+ clickOn(CommonStrings.action_end_poll)
// A confirmation dialog should be shown
eventsRecorder.assertEmpty()
- rule.pressTag(TestTags.dialogPositive.value)
+ pressTag(TestTags.dialogPositive.value)
eventsRecorder.assertSingle(TimelineEvent.EndPoll(content.eventId!!))
}
}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt
index 154225aa7a..7b8597f05a 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt
@@ -6,14 +6,17 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.timeline.components.event
import android.text.SpannableString
import android.text.SpannedString
import androidx.activity.ComponentActivity
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -38,45 +41,40 @@ import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.wysiwyg.view.spans.CustomMentionSpan
import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.test.runTest
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TimelineTextViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
private val mentionSpanTheme = MentionSpanTheme(currentUserId = A_USER_ID)
private val formatLambda = lambdaRecorder { mentionType -> mentionType.toString() }
private val mentionSpanFormatter = FakeMentionSpanFormatter(formatLambda)
@Test
- fun `getTextWithResolvedMentions - does nothing for a non spannable CharSequence`() = runTest {
+ fun `getTextWithResolvedMentions - does nothing for a non spannable CharSequence`() = runAndroidComposeUiTest {
val charSequence = "Hello @alice:example.com"
val mentionSpanUpdater = aMentionSpanUpdater()
- val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
+ val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
assertThat(result.getMentionSpans()).isEmpty()
assert(formatLambda).isNeverCalled()
}
@Test
- fun `getTextWithResolvedMentions - does nothing if there are no mentions`() = runTest {
+ fun `getTextWithResolvedMentions - does nothing if there are no mentions`() = runAndroidComposeUiTest {
val charSequence = SpannableString("Hello @alice:example.com")
val mentionSpanUpdater = aMentionSpanUpdater()
- val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
+ val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
assertThat(result.getMentionSpans()).isEmpty()
assert(formatLambda).isNeverCalled()
}
@Test
- fun `getTextWithResolvedMentions - just returns the body if there is no formattedBody`() = runTest {
+ fun `getTextWithResolvedMentions - just returns the body if there is no formattedBody`() = runAndroidComposeUiTest {
val charSequence = "Hello @alice:example.com"
val mentionSpanUpdater = aMentionSpanUpdater()
- val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(body = charSequence, formattedBody = null))
+ val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(body = charSequence, formattedBody = null))
assertThat(result.getMentionSpans()).isEmpty()
assertThat(result.toString()).isEqualTo(charSequence)
@@ -84,7 +82,7 @@ class TimelineTextViewTest {
}
@Test
- fun `getTextWithResolvedMentions - with Room mention format correctly`() = runTest {
+ fun `getTextWithResolvedMentions - with Room mention format correctly`() = runAndroidComposeUiTest {
val mentionType = MentionType.Room(roomIdOrAlias = A_ROOM_ID_2.toRoomIdOrAlias())
val charSequence = buildSpannedString {
append("Hello ")
@@ -93,7 +91,7 @@ class TimelineTextViewTest {
}
}
val mentionSpanUpdater = aMentionSpanUpdater()
- val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
+ val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
val expectedDisplayText = mentionType.toString()
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
@@ -102,7 +100,7 @@ class TimelineTextViewTest {
}
@Test
- fun `getTextWithResolvedMentions - replaces MentionSpan's text`() = runTest {
+ fun `getTextWithResolvedMentions - replaces MentionSpan's text`() = runAndroidComposeUiTest {
val mentionType = MentionType.User(userId = A_USER_ID)
val charSequence = buildSpannedString {
append("Hello ")
@@ -111,7 +109,7 @@ class TimelineTextViewTest {
}
}
val mentionSpanUpdater = aMentionSpanUpdater()
- val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
+ val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
val expectedDisplayText = mentionType.toString()
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
@@ -119,7 +117,7 @@ class TimelineTextViewTest {
}
@Test
- fun `getTextWithResolvedMentions - replaces MentionSpan's text inside CustomMentionSpan`() = runTest {
+ fun `getTextWithResolvedMentions - replaces MentionSpan's text inside CustomMentionSpan`() = runAndroidComposeUiTest {
val mentionType = MentionType.User(userId = A_USER_ID)
val charSequence = buildSpannedString {
append("Hello ")
@@ -129,12 +127,12 @@ class TimelineTextViewTest {
}
val mentionSpanUpdater = aMentionSpanUpdater()
val expectedDisplayText = mentionType.toString()
- val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
+ val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
assert(formatLambda).isCalledOnce()
}
- private suspend fun AndroidComposeTestRule.getText(
+ private suspend fun AndroidComposeUiTest.getText(
mentionSpanUpdater: MentionSpanUpdater,
content: TimelineItemTextBasedContent,
): CharSequence {
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt
index af3acee6a2..8050278fb2 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt
@@ -6,56 +6,55 @@
* Please see LICENSE files in the repository root for full details.
*/
+@file:OptIn(ExperimentalTestApi::class)
+
package io.element.android.features.messages.impl.timeline.protection
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.AndroidComposeUiTest
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.lambda.lambdaError
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ProtectedViewTest {
- @get:Rule val rule = createAndroidComposeRule()
-
@Test
- fun `when hideContent is false, the content is rendered`() {
- rule.setProtectedView(
+ fun `when hideContent is false, the content is rendered`() = runAndroidComposeUiTest {
+ setProtectedView(
hideContent = false,
content = {
Text("Hello")
}
)
- rule.onNodeWithText("Hello").assertExists()
+ onNodeWithText("Hello").assertExists()
}
@Test
- fun `when hideContent is true, the content is not rendered, and user can reveal it`() {
+ fun `when hideContent is true, the content is not rendered, and user can reveal it`() = runAndroidComposeUiTest {
ensureCalledOnce {
- rule.setProtectedView(
+ setProtectedView(
hideContent = true,
onShowClick = it,
content = {
Text("Hello")
}
)
- rule.onNodeWithText("Hello").assertDoesNotExist()
- rule.clickOn(CommonStrings.action_show)
+ onNodeWithText("Hello").assertDoesNotExist()
+ clickOn(CommonStrings.action_show)
}
}
}
-private fun AndroidComposeTestRule