From a88b3699a32de23ae87cabc7e23158fc20e6a649 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 22 Aug 2023 10:17:55 +0200 Subject: [PATCH 01/26] Code clarity (no change effect): always use when with `ButtonStyle.Filled`, `ButtonStyle.Outlined` and `ButtonStyle.Text` cases. --- .../designsystem/theme/components/Button.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt index 8c5d96c400..99d2a68036 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt @@ -136,29 +136,33 @@ internal fun ButtonInternal( val contentPadding = when (size) { ButtonSize.Medium -> { when (style) { + ButtonStyle.Filled, + ButtonStyle.Outlined -> PaddingValues(horizontal = 16.dp, vertical = 10.dp) ButtonStyle.Text -> PaddingValues(horizontal = 12.dp, vertical = 10.dp) - else -> PaddingValues(horizontal = 16.dp, vertical = 10.dp) } } ButtonSize.Large -> { when (style) { + ButtonStyle.Filled, + ButtonStyle.Outlined -> PaddingValues(horizontal = 24.dp, vertical = 13.dp) ButtonStyle.Text -> PaddingValues(horizontal = 16.dp, vertical = 13.dp) - else -> PaddingValues(horizontal = 24.dp, vertical = 13.dp) } } } val shape = when (style) { - ButtonStyle.Filled, ButtonStyle.Outlined -> RoundedCornerShape(percent = 50) + ButtonStyle.Filled, + ButtonStyle.Outlined -> RoundedCornerShape(percent = 50) ButtonStyle.Text -> RectangleShape } val border = when (style) { - ButtonStyle.Filled, ButtonStyle.Text -> null + ButtonStyle.Filled -> null ButtonStyle.Outlined -> BorderStroke( width = 1.dp, color = ElementTheme.colors.borderInteractiveSecondary ) + ButtonStyle.Text -> null } val textStyle = when (size) { @@ -166,9 +170,10 @@ internal fun ButtonInternal( ButtonSize.Large -> ElementTheme.typography.fontBodyLgMedium } - val internalPadding = when { - style == ButtonStyle.Text -> if (leadingIcon != null) PaddingValues(start = 8.dp) else PaddingValues(0.dp) - else -> PaddingValues(horizontal = 8.dp) + val internalPadding = when (style) { + ButtonStyle.Filled, + ButtonStyle.Outlined -> PaddingValues(horizontal = 8.dp) + ButtonStyle.Text -> if (leadingIcon != null) PaddingValues(start = 8.dp) else PaddingValues(0.dp) } androidx.compose.material3.Button( From 448e526ffa376c70c5456fdcb6f38d4ea7a53a4f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 22 Aug 2023 10:38:24 +0200 Subject: [PATCH 02/26] Button theme: fix small integration mistake on button padding from Figma. --- .../designsystem/theme/components/Button.kt | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt index 99d2a68036..ccc014b2c8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt @@ -133,23 +133,39 @@ internal fun ButtonInternal( ButtonSize.Large -> 48.dp } - val contentPadding = when (size) { - ButtonSize.Medium -> { - when (style) { - ButtonStyle.Filled, - ButtonStyle.Outlined -> PaddingValues(horizontal = 16.dp, vertical = 10.dp) - ButtonStyle.Text -> PaddingValues(horizontal = 12.dp, vertical = 10.dp) - } + val hasNoStartDrawable = !showProgress && leadingIcon == null + + val (paddingStart, paddingCenter, paddingEnd) = when (size) { + ButtonSize.Medium -> when (style) { + ButtonStyle.Filled, + ButtonStyle.Outlined -> if (hasNoStartDrawable) + Triple(24.dp, 0.dp, 24.dp) + else + Triple(16.dp, 8.dp, 24.dp) + ButtonStyle.Text -> if (hasNoStartDrawable) + Triple(12.dp, 0.dp, 12.dp) + else + Triple(12.dp, 8.dp, 16.dp) } - ButtonSize.Large -> { - when (style) { - ButtonStyle.Filled, - ButtonStyle.Outlined -> PaddingValues(horizontal = 24.dp, vertical = 13.dp) - ButtonStyle.Text -> PaddingValues(horizontal = 16.dp, vertical = 13.dp) - } + ButtonSize.Large -> when (style) { + ButtonStyle.Filled, + ButtonStyle.Outlined -> if (hasNoStartDrawable) + Triple(32.dp, 0.dp, 32.dp) + else + Triple(24.dp, 8.dp, 32.dp) + ButtonStyle.Text -> if (hasNoStartDrawable) + Triple(16.dp, 0.dp, 16.dp) + else + Triple(12.dp, 8.dp, 16.dp) } } + val contentPadding = when (size) { + ButtonSize.Medium -> PaddingValues(start = paddingStart, end = paddingEnd, top = 10.dp, bottom = 10.dp) + ButtonSize.Large -> PaddingValues(start = paddingStart, end = paddingEnd, top = 13.dp, bottom = 13.dp) + } + val internalPadding = PaddingValues(start = paddingCenter) + val shape = when (style) { ButtonStyle.Filled, ButtonStyle.Outlined -> RoundedCornerShape(percent = 50) @@ -170,12 +186,6 @@ internal fun ButtonInternal( ButtonSize.Large -> ElementTheme.typography.fontBodyLgMedium } - val internalPadding = when (style) { - ButtonStyle.Filled, - ButtonStyle.Outlined -> PaddingValues(horizontal = 8.dp) - ButtonStyle.Text -> if (leadingIcon != null) PaddingValues(start = 8.dp) else PaddingValues(0.dp) - } - androidx.compose.material3.Button( onClick = { if (!showProgress) { From b363080370c10ae28c83d4c0c59972ca7ba2fcc5 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 22 Aug 2023 08:49:40 +0000 Subject: [PATCH 03/26] Update screenshots --- ...ts_null_Buttons_TextButtonLarge_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...s_null_Buttons_TextButtonMedium_0_null,NEXUS_5,1.0,en].png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Buttons_TextButtonLarge_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Buttons_TextButtonLarge_0_null,NEXUS_5,1.0,en].png index 775891e893..1f610f0fa1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Buttons_TextButtonLarge_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Buttons_TextButtonLarge_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83ae98e86b9e229c82e66e495ce9130f8aac3fd3364479409dd8aec2f60f5c44 -size 32430 +oid sha256:d8233ab7e58175a8dbd14aa8c30dda882176099232cd56417102fa3675efe92b +size 31114 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Buttons_TextButtonMedium_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Buttons_TextButtonMedium_0_null,NEXUS_5,1.0,en].png index 5eb71bc999..9c23dea6cf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Buttons_TextButtonMedium_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Buttons_TextButtonMedium_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8e9ce0aafcaa2c873d706c6e477f2103c8f930f0c15e186a981eaa831e51bfc -size 30609 +oid sha256:974dd693b78cc08e278ff81a65e234629cdbf1075d3fbf255895cf1ee2eb906c +size 29264 From e45b15165a353b8bd923eb77f1f06c0c4e749174 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 22 Aug 2023 15:21:43 +0200 Subject: [PATCH 04/26] Make links in messages clickable again (#1115) --- changelog.d/1111.bugfix | 1 + .../components/event/TimelineItemTextView.kt | 1 - .../impl/timeline/components/html/HtmlDocument.kt | 1 - .../designsystem/components/ClickableLinkText.kt | 13 ++++++++----- 4 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 changelog.d/1111.bugfix diff --git a/changelog.d/1111.bugfix b/changelog.d/1111.bugfix new file mode 100644 index 0000000000..4d1baf87bf --- /dev/null +++ b/changelog.d/1111.bugfix @@ -0,0 +1 @@ +Make links in messages clickable again. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index bc06afcd61..7b04d60ade 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -66,7 +66,6 @@ fun TimelineItemTextView( } ClickableLinkText( text = textWithPadding, - linkAnnotationTag = "URL", onClick = onTextClicked, onLongClick = onTextLongClicked, interactionSource = interactionSource diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/html/HtmlDocument.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/html/HtmlDocument.kt index 8179f55877..9c2798a638 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/html/HtmlDocument.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/html/HtmlDocument.kt @@ -582,7 +582,6 @@ private fun HtmlText( val inlineContentMap = persistentMapOf() ClickableLinkText( annotatedString = text, - linkAnnotationTag = "URL", style = style, modifier = modifier, inlineContent = inlineContentMap, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt index bd5f393cd2..39838a218a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt @@ -48,13 +48,15 @@ import io.element.android.libraries.theme.LinkColor import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf +const val LINK_TAG = "URL" + @Composable fun ClickableLinkText( text: String, interactionSource: MutableInteractionSource, modifier: Modifier = Modifier, linkify: Boolean = true, - linkAnnotationTag: String = "", + linkAnnotationTag: String = LINK_TAG, onClick: () -> Unit = {}, onLongClick: () -> Unit = {}, style: TextStyle = LocalTextStyle.current, @@ -80,13 +82,14 @@ fun ClickableLinkText( interactionSource: MutableInteractionSource, modifier: Modifier = Modifier, linkify: Boolean = true, - linkAnnotationTag: String = "", + linkAnnotationTag: String = LINK_TAG, onClick: () -> Unit = {}, onLongClick: () -> Unit = {}, style: TextStyle = LocalTextStyle.current, inlineContent: ImmutableMap = persistentMapOf(), ) { - val processedText = remember(annotatedString) { + @Suppress("NAME_SHADOWING") + val annotatedString = remember(annotatedString) { if (linkify) { annotatedString.linkify(SpanStyle(color = LinkColor)) } else { @@ -126,7 +129,7 @@ fun ClickableLinkText( } } Text( - text = processedText, + text = annotatedString, modifier = modifier.then(pressIndicator), style = style, onTextLayout = { @@ -158,7 +161,7 @@ fun AnnotatedString.linkify(linkStyle: SpanStyle): AnnotatedString { style = linkStyle, ) addStringAnnotation( - tag = "URL", + tag = LINK_TAG, annotation = span.url, start = start, end = end From 8c9643f8b806363ee16c920f0ebb11ebac3bf0f9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 22 Aug 2023 18:10:49 +0200 Subject: [PATCH 05/26] Simplify code (code review). --- .../designsystem/theme/components/Button.kt | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt index ccc014b2c8..ff613226e2 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding @@ -133,39 +134,33 @@ internal fun ButtonInternal( ButtonSize.Large -> 48.dp } - val hasNoStartDrawable = !showProgress && leadingIcon == null + val hasStartDrawable = showProgress || leadingIcon != null - val (paddingStart, paddingCenter, paddingEnd) = when (size) { + val contentPadding = when (size) { ButtonSize.Medium -> when (style) { ButtonStyle.Filled, - ButtonStyle.Outlined -> if (hasNoStartDrawable) - Triple(24.dp, 0.dp, 24.dp) + ButtonStyle.Outlined -> if (hasStartDrawable) + PaddingValues(start = 16.dp, top = 10.dp, end = 24.dp, bottom = 10.dp) else - Triple(16.dp, 8.dp, 24.dp) - ButtonStyle.Text -> if (hasNoStartDrawable) - Triple(12.dp, 0.dp, 12.dp) + PaddingValues(start = 24.dp, top = 10.dp, end = 24.dp, bottom = 10.dp) + ButtonStyle.Text -> if (hasStartDrawable) + PaddingValues(start = 12.dp, top = 10.dp, end = 16.dp, bottom = 10.dp) else - Triple(12.dp, 8.dp, 16.dp) + PaddingValues(start = 12.dp, top = 10.dp, end = 12.dp, bottom = 10.dp) } ButtonSize.Large -> when (style) { ButtonStyle.Filled, - ButtonStyle.Outlined -> if (hasNoStartDrawable) - Triple(32.dp, 0.dp, 32.dp) + ButtonStyle.Outlined -> if (hasStartDrawable) + PaddingValues(start = 24.dp, top = 13.dp, end = 32.dp, bottom = 13.dp) else - Triple(24.dp, 8.dp, 32.dp) - ButtonStyle.Text -> if (hasNoStartDrawable) - Triple(16.dp, 0.dp, 16.dp) + PaddingValues(start = 32.dp, top = 13.dp, end = 32.dp, bottom = 13.dp) + ButtonStyle.Text -> if (hasStartDrawable) + PaddingValues(start = 12.dp, top = 13.dp, end = 16.dp, bottom = 13.dp) else - Triple(12.dp, 8.dp, 16.dp) + PaddingValues(start = 16.dp, top = 13.dp, end = 16.dp, bottom = 13.dp) } } - val contentPadding = when (size) { - ButtonSize.Medium -> PaddingValues(start = paddingStart, end = paddingEnd, top = 10.dp, bottom = 10.dp) - ButtonSize.Large -> PaddingValues(start = paddingStart, end = paddingEnd, top = 13.dp, bottom = 13.dp) - } - val internalPadding = PaddingValues(start = paddingCenter) - val shape = when (style) { ButtonStyle.Filled, ButtonStyle.Outlined -> RoundedCornerShape(percent = 50) @@ -210,6 +205,7 @@ internal fun ButtonInternal( color = LocalContentColor.current, strokeWidth = 2.dp, ) + Spacer(modifier = Modifier.width(8.dp)) } leadingIcon != null -> { androidx.compose.material.Icon( @@ -218,15 +214,14 @@ internal fun ButtonInternal( tint = LocalContentColor.current, modifier = Modifier.size(20.dp), ) + Spacer(modifier = Modifier.width(8.dp)) } - else -> Unit } Text( text = text, style = textStyle, maxLines = 1, overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(internalPadding), ) } } From 2c39a6173a292ef68c4e70000d2816a37678c3de Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 07:52:59 +0200 Subject: [PATCH 06/26] Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.6.0 (#1121) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73a9e85b3b..d79df0df1a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ test_core = "1.5.0" #other coil = "2.4.0" datetime = "0.4.0" -serialization_json = "1.5.1" +serialization_json = "1.6.0" showkase = "1.0.0-beta18" jsoup = "1.16.1" appyx = "1.3.0" From 92fe22d9d7733a5296dbc08020ea7cf8631044a0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:44:06 +0000 Subject: [PATCH 07/26] Update dependency org.matrix.rustcomponents:sdk-android to v0.1.45 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d79df0df1a..d664e32530 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -146,7 +146,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.44" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.45" sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } From 09efc2d3585d2b00c1492b4d88cf3f6deba60c71 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 23 Aug 2023 10:59:09 +0100 Subject: [PATCH 08/26] Update localazy config generator - specify force_underscore=yes (#1122) --- tools/localazy/generateLocalazyConfig.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/localazy/generateLocalazyConfig.py b/tools/localazy/generateLocalazyConfig.py index 6dfffa9c69..464c67c70a 100755 --- a/tools/localazy/generateLocalazyConfig.py +++ b/tools/localazy/generateLocalazyConfig.py @@ -25,6 +25,9 @@ baseAction = { # Replacement done in all string values "replacements": { "...": "…" + }, + "params": { + "force_underscore": "yes" } } From 06a9b129d0534a06efa86dc1955fe977654f72ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 11:55:05 +0200 Subject: [PATCH 09/26] Restore OIDC support. --- docs/oidc.md | 4 ++ .../ConfirmAccountProviderPresenter.kt | 45 +++++++++++++- .../login/impl/util/LoginConstants.kt | 2 +- .../ConfirmAccountProviderPresenterTest.kt | 59 ++++++++++--------- .../logout/api/LogoutPreferenceScreen.kt | 4 +- .../logout/api/LogoutPreferenceState.kt | 2 +- .../impl/DefaultLogoutPreferencePresenter.kt | 4 +- .../impl/root/PreferencesRootNode.kt | 15 ++++- .../impl/root/PreferencesRootView.kt | 3 + .../libraries/matrix/api/MatrixClient.kt | 7 ++- .../api/auth/AuthenticationException.kt | 3 +- .../libraries/matrix/impl/RustMatrixClient.kt | 29 +++++---- .../matrix/impl/RustMatrixClientFactory.kt | 1 + .../impl/auth/AuthenticationException.kt | 7 +-- .../matrix/impl/auth/HomeserverDetails.kt | 2 +- .../libraries/matrix/impl/auth/OidcConfig.kt | 18 +++--- .../auth/RustMatrixAuthenticationService.kt | 35 ++++------- .../libraries/matrix/test/FakeMatrixClient.kt | 3 +- .../sessionstorage/api/SessionData.kt | 1 + .../sessionstorage/impl/SessionDataMapper.kt | 2 + .../libraries/matrix/session/SessionData.sq | 3 +- .../impl/src/main/sqldelight/migrations/2.sqm | 1 + .../impl/DatabaseSessionStoreTests.kt | 1 + 23 files changed, 164 insertions(+), 87 deletions(-) create mode 100644 libraries/session-storage/impl/src/main/sqldelight/migrations/2.sqm diff --git a/docs/oidc.md b/docs/oidc.md index 5f9e70268d..0e4ad44852 100644 --- a/docs/oidc.md +++ b/docs/oidc.md @@ -45,3 +45,7 @@ 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 + + +Test server: +synapse-oidc.lab.element.dev diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 1a021ad605..61ef4d724b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -17,6 +17,7 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -26,8 +27,11 @@ import androidx.compose.runtime.rememberCoroutineScope import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.element.android.features.login.api.oidc.OidcAction +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError +import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState @@ -40,7 +44,9 @@ import java.net.URL class ConfirmAccountProviderPresenter @AssistedInject constructor( @Assisted private val params: Params, private val accountProviderDataSource: AccountProviderDataSource, - private val authenticationService: MatrixAuthenticationService + private val authenticationService: MatrixAuthenticationService, + private val defaultOidcActionFlow: DefaultOidcActionFlow, + private val defaultLoginUserStory: DefaultLoginUserStory, ) : Presenter { data class Params( @@ -61,6 +67,14 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( mutableStateOf(Async.Uninitialized) } + LaunchedEffect(Unit) { + launch { + defaultOidcActionFlow.collect { + onOidcAction(it, loginFlowAction) + } + } + } + fun handleEvents(event: ConfirmAccountProviderEvents) { when (event) { ConfirmAccountProviderEvents.Continue -> { @@ -97,4 +111,33 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( }.getOrThrow() }.runCatchingUpdatingState(loginFlowAction, errorTransform = ChangeServerError::from) } + + private suspend fun onOidcAction( + oidcAction: OidcAction?, + loginFlowAction: MutableState>, + ) { + oidcAction ?: return + loginFlowAction.value = Async.Loading() + when (oidcAction) { + OidcAction.GoBack -> { + authenticationService.cancelOidcLogin() + .onSuccess { + loginFlowAction.value = Async.Uninitialized + } + .onFailure { failure -> + loginFlowAction.value = Async.Failure(failure) + } + } + is OidcAction.Success -> { + authenticationService.loginWithOidc(oidcAction.url) + .onSuccess { _ -> + defaultLoginUserStory.setLoginFlowIsDone(true) + } + .onFailure { failure -> + loginFlowAction.value = Async.Failure(failure) + } + } + } + defaultOidcActionFlow.reset() + } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt index e8bcea990e..152b1a094c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt @@ -21,7 +21,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider object LoginConstants { const val MATRIX_ORG_URL = "matrix.org" - const val DEFAULT_HOMESERVER_URL = "matrix.org" // TODO Oidc "synapse-oidc.lab.element.dev" + const val DEFAULT_HOMESERVER_URL = "matrix.org" const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md" } 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 76a3ad3d22..73f98a2667 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 @@ -20,9 +20,12 @@ 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.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource +import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC import io.element.android.libraries.matrix.test.A_THROWABLE @@ -33,11 +36,7 @@ import org.junit.Test class ConfirmAccountProviderPresenterTest { @Test fun `present - initial test`() = runTest { - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - FakeAuthenticationService(), - ) + val presenter = createConfirmAccountProviderPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -51,13 +50,11 @@ class ConfirmAccountProviderPresenterTest { @Test fun `present - continue password login`() = runTest { - val authServer = FakeAuthenticationService() - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - authServer, + val authenticationService = FakeAuthenticationService() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, ) - authServer.givenHomeserver(A_HOMESERVER) + authenticationService.givenHomeserver(A_HOMESERVER) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -75,13 +72,11 @@ class ConfirmAccountProviderPresenterTest { @Test fun `present - continue oidc`() = runTest { - val authServer = FakeAuthenticationService() - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - authServer, + val authenticationService = FakeAuthenticationService() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, ) - authServer.givenHomeserver(A_HOMESERVER_OIDC) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -99,17 +94,15 @@ class ConfirmAccountProviderPresenterTest { @Test fun `present - submit fails`() = runTest { - val authServer = FakeAuthenticationService() - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - authServer, + val authenticationService = FakeAuthenticationService() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() - authServer.givenChangeServerError(Throwable()) + authenticationService.givenChangeServerError(Throwable()) initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) skipItems(1) // Loading val failureState = awaitItem() @@ -121,10 +114,8 @@ class ConfirmAccountProviderPresenterTest { @Test fun `present - clear error`() = runTest { val authenticationService = FakeAuthenticationService() - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - authenticationService, + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -147,4 +138,18 @@ class ConfirmAccountProviderPresenterTest { assertThat(clearedState.loginFlow).isEqualTo(Async.Uninitialized) } } + + private fun createConfirmAccountProviderPresenter( + params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false), + accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(), + matrixAuthenticationService: MatrixAuthenticationService = FakeAuthenticationService(), + defaultOidcActionFlow: DefaultOidcActionFlow = DefaultOidcActionFlow(), + defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(), + ) = ConfirmAccountProviderPresenter( + params = params, + accountProviderDataSource = accountProviderDataSource, + authenticationService = matrixAuthenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + defaultLoginUserStory = defaultLoginUserStory, + ) } diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt index 60844f4477..f9f23ac9c4 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt @@ -34,12 +34,12 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight @Composable fun LogoutPreferenceView( state: LogoutPreferenceState, - onSuccessLogout: () -> Unit = {} + onSuccessLogout: (String?) -> Unit = {} ) { val eventSink = state.eventSink if (state.logoutAction is Async.Success) { LaunchedEffect(state.logoutAction) { - onSuccessLogout() + onSuccessLogout(state.logoutAction.data) } return } diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceState.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceState.kt index e5fd05ba8e..95550ef4c8 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceState.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceState.kt @@ -19,6 +19,6 @@ package io.element.android.features.logout.api import io.element.android.libraries.architecture.Async data class LogoutPreferenceState( - val logoutAction: Async, + val logoutAction: Async, val eventSink: (LogoutPreferenceEvents) -> Unit, ) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutPreferencePresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutPreferencePresenter.kt index e957755b98..2fece4449b 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutPreferencePresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutPreferencePresenter.kt @@ -40,7 +40,7 @@ class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixCli @Composable override fun present(): LogoutPreferenceState { val localCoroutineScope = rememberCoroutineScope() - val logoutAction: MutableState> = remember { + val logoutAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } @@ -56,7 +56,7 @@ class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixCli ) } - private fun CoroutineScope.logout(logoutAction: MutableState>) = launch { + private fun CoroutineScope.logout(logoutAction: MutableState>) = launch { suspend { matrixClient.logout() }.runCatchingUpdatingState(logoutAction) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index a564927101..3f897caf03 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -16,8 +16,10 @@ package io.element.android.features.preferences.impl.root +import android.app.Activity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -25,7 +27,9 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.SessionScope +import timber.log.Timber @ContributesNode(SessionScope::class) class PreferencesRootNode @AssistedInject constructor( @@ -65,6 +69,7 @@ class PreferencesRootNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { val state = presenter.present() + val activity = LocalContext.current as Activity PreferencesRootView( state = state, modifier = modifier, @@ -73,7 +78,15 @@ class PreferencesRootNode @AssistedInject constructor( onOpenAnalytics = this::onOpenAnalytics, onOpenAbout = this::onOpenAbout, onVerifyClicked = this::onVerifyClicked, - onOpenDeveloperSettings = this::onOpenDeveloperSettings + onOpenDeveloperSettings = this::onOpenDeveloperSettings, + onSuccessLogout = { onSuccessLogout(activity, it) } ) } + + private fun onSuccessLogout(activity: Activity, url: String?) { + Timber.d("Success logout with result url: $url") + url?.let { + activity.openUrlInChromeCustomTab(null, false, it) + } + } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 4b589ad2b0..1cb3a37c77 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -55,6 +55,7 @@ fun PreferencesRootView( onOpenRageShake: () -> Unit, onOpenAbout: () -> Unit, onOpenDeveloperSettings: () -> Unit, + onSuccessLogout: (String?) -> Unit, modifier: Modifier = Modifier, ) { val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) @@ -98,6 +99,7 @@ fun PreferencesRootView( HorizontalDivider() LogoutPreferenceView( state = state.logoutState, + onSuccessLogout = onSuccessLogout, ) Text( modifier = Modifier @@ -140,5 +142,6 @@ private fun ContentToPreview(matrixUser: MatrixUser) { onOpenDeveloperSettings = {}, onOpenAbout = {}, onVerifyClicked = {}, + onSuccessLogout = {}, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 67c0625a91..705cbbc620 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -55,7 +55,12 @@ interface MatrixClient : Closeable { * Will close the client and delete the cache data. */ suspend fun clearCache() - suspend fun logout() + + /** + * Logout the user. + * Returns an optional URL. When the URL is there, it should be presented to the user after logout for RP initiated logout on their account page. + */ + suspend fun logout(): String? suspend fun loadUserDisplayName(): Result suspend fun loadUserAvatarURLString(): Result suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt index 48712b7ddf..9620dc6d7f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt @@ -22,6 +22,5 @@ sealed class AuthenticationException(message: String) : Exception(message) { class SlidingSyncNotAvailable(message: String) : AuthenticationException(message) class SessionMissing(message: String) : AuthenticationException(message) class Generic(message: String) : AuthenticationException(message) - // TODO Oidc - // class OidcError(type: String, message: String) : AuthenticationException(message) + data class OidcError(val type: String, override val message: String) : AuthenticationException(message) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 8f5cfa496b..e7b1ad4d5e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -119,6 +119,11 @@ class RustMatrixClient constructor( Timber.v("didReceiveAuthError -> already cleaning up") } } + + override fun didRefreshTokens() { + Timber.w("didRefreshTokens()") + // TODO handle refresh token + } } private val rustRoomListService: RoomListService = @@ -287,19 +292,23 @@ class RustMatrixClient constructor( baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = false) } - override suspend fun logout() = doLogout(doRequest = true) + override suspend fun logout(): String? = doLogout(doRequest = true) - private suspend fun doLogout(doRequest: Boolean) = withContext(sessionDispatcher) { - if (doRequest) { - try { - client.logout() - } catch (failure: Throwable) { - Timber.e(failure, "Fail to call logout on HS. Still delete local files.") + private suspend fun doLogout(doRequest: Boolean): String? { + var result: String? = null + withContext(sessionDispatcher) { + if (doRequest) { + try { + result = client.logout() + } catch (failure: Throwable) { + Timber.e(failure, "Fail to call logout on HS. Still delete local files.") + } } + close() + baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = true) + sessionStore.removeSession(sessionId.value) } - close() - baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = true) - sessionStore.removeSession(sessionId.value) + return result } override suspend fun loadUserDisplayName(): Result = withContext(sessionDispatcher) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 931133c266..bb80032230 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -75,4 +75,5 @@ private fun SessionData.toSession() = Session( deviceId = deviceId, homeserverUrl = homeserverUrl, slidingSyncProxy = slidingSyncProxy, + oidcData = oidcData, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt index f0feb2857d..8f7f63e503 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt @@ -26,15 +26,12 @@ fun Throwable.mapAuthenticationException(): AuthenticationException { is RustAuthenticationException.InvalidServerName -> AuthenticationException.InvalidServerName(this.message!!) is RustAuthenticationException.SessionMissing -> AuthenticationException.SessionMissing(this.message!!) is RustAuthenticationException.SlidingSyncNotAvailable -> AuthenticationException.SlidingSyncNotAvailable(this.message!!) - - /* TODO Oidc is RustAuthenticationException.OidcException -> AuthenticationException.OidcError("OidcException", message!!) is RustAuthenticationException.OidcMetadataInvalid -> AuthenticationException.OidcError("OidcMetadataInvalid", message!!) is RustAuthenticationException.OidcMetadataMissing -> AuthenticationException.OidcError("OidcMetadataMissing", message!!) - is RustAuthenticationException.OidcNotStarted -> AuthenticationException.OidcError("OidcNotStarted", message!!) is RustAuthenticationException.OidcNotSupported -> AuthenticationException.OidcError("OidcNotSupported", message!!) - */ - + is RustAuthenticationException.OidcCancelled -> AuthenticationException.OidcError("OidcCancelled", message!!) + is RustAuthenticationException.OidcCallbackUrlInvalid -> AuthenticationException.OidcError("OidcCallbackUrlInvalid", message!!) else -> AuthenticationException.Generic(this.message ?: "Unknown error") } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt index a3d277c6da..f1d3e34bf8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt @@ -23,6 +23,6 @@ fun HomeserverLoginDetails.map(): MatrixHomeServerDetails = use { MatrixHomeServerDetails( url = url(), supportsPasswordLogin = supportsPasswordLogin(), - supportsOidcLogin = false // TODO Oidc supportsOidcLogin(), + supportsOidcLogin = supportsOidcLogin(), ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt index b5115ffad4..401fa0ce83 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt @@ -16,17 +16,19 @@ package io.element.android.libraries.matrix.impl.auth -// TODO Oidc -// import io.element.android.libraries.matrix.api.auth.OidcConfig -// import org.matrix.rustcomponents.sdk.OidcClientMetadata +import io.element.android.libraries.matrix.api.auth.OidcConfig +import org.matrix.rustcomponents.sdk.OidcConfiguration -/* -val oidcClientMetadata: OidcClientMetadata = OidcClientMetadata( +val oidcConfiguration: OidcConfiguration = OidcConfiguration( clientName = "Element", redirectUri = OidcConfig.redirectUri, clientUri = "https://element.io", tosUri = "https://element.io/user-terms-of-service", - policyUri = "https://element.io/privacy" + policyUri = "https://element.io/privacy", + /** + * Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually + */ + staticRegistrations = mapOf( + "https://id.thirdroom.io/realms/thirdroom" to "elementx", + ), ) - */ - diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index ba06891013..fe6bf7dea9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -16,8 +16,6 @@ package io.element.android.libraries.matrix.impl.auth -// TODO Oidc -// import org.matrix.rustcomponents.sdk.OidcAuthenticationUrl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.mapFailure @@ -37,6 +35,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.OidcAuthenticationData import org.matrix.rustcomponents.sdk.Session import org.matrix.rustcomponents.sdk.use import java.io.File @@ -57,9 +56,8 @@ class RustMatrixAuthenticationService @Inject constructor( private val authService: RustAuthenticationService = RustAuthenticationService( basePath = baseDirectory.absolutePath, passphrase = null, - // TODO Oidc - // oidcClientMetadata = oidcClientMetadata, userAgent = userAgentProvider.provide(), + oidcConfiguration = oidcConfiguration, customSlidingSyncProxy = null, ) private var currentHomeserver = MutableStateFlow(null) @@ -112,60 +110,50 @@ class RustMatrixAuthenticationService @Inject constructor( } } - // TODO Oidc - // private var pendingUrlForOidcLogin: OidcAuthenticationUrl? = null + private var pendingOidcAuthenticationData: OidcAuthenticationData? = null override suspend fun getOidcUrl(): Result { - TODO("Oidc") - /* return withContext(coroutineDispatchers.io) { runCatching { - val urlForOidcLogin = authService.urlForOidcLogin() - val url = urlForOidcLogin.loginUrl() - pendingUrlForOidcLogin = urlForOidcLogin + val oidcAuthenticationData = authService.urlForOidcLogin() + val url = oidcAuthenticationData.loginUrl() + pendingOidcAuthenticationData = oidcAuthenticationData OidcDetails(url) }.mapFailure { failure -> failure.mapAuthenticationException() } } - */ } override suspend fun cancelOidcLogin(): Result { - TODO("Oidc") - /* return withContext(coroutineDispatchers.io) { runCatching { - pendingUrlForOidcLogin?.close() - pendingUrlForOidcLogin = null + pendingOidcAuthenticationData?.close() + pendingOidcAuthenticationData = null }.mapFailure { failure -> failure.mapAuthenticationException() } } - */ } /** * callbackUrl should be the uriRedirect from OidcClientMetadata (with all the parameters). */ override suspend fun loginWithOidc(callbackUrl: String): Result { - TODO("Oidc") - /* return withContext(coroutineDispatchers.io) { runCatching { - val urlForOidcLogin = pendingUrlForOidcLogin ?: error("You need to call `getOidcUrl()` first") + val urlForOidcLogin = pendingOidcAuthenticationData ?: error("You need to call `getOidcUrl()` first") val client = authService.loginWithOidcCallback(urlForOidcLogin, callbackUrl) val sessionData = client.use { it.session().toSessionData() } - pendingUrlForOidcLogin = null + pendingOidcAuthenticationData?.close() + pendingOidcAuthenticationData = null sessionStore.storeData(sessionData) SessionId(sessionData.userId) }.mapFailure { failure -> failure.mapAuthenticationException() } } - */ } - } private fun Session.toSessionData() = SessionData( @@ -174,6 +162,7 @@ private fun Session.toSessionData() = SessionData( accessToken = accessToken, refreshToken = refreshToken, homeserverUrl = homeserverUrl, + oidcData = oidcData, slidingSyncProxy = slidingSyncProxy, loginTimestamp = Date(), ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 1229836e30..1734aec718 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -109,9 +109,10 @@ class FakeMatrixClient( override suspend fun clearCache() { } - override suspend fun logout() { + override suspend fun logout(): String? { delay(100) logoutFailure?.let { throw it } + return null } override fun close() = Unit diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt index cc106f960a..e14c3feeab 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt @@ -24,6 +24,7 @@ data class SessionData( val accessToken: String, val refreshToken: String?, val homeserverUrl: String, + val oidcData: String?, val slidingSyncProxy: String?, val loginTimestamp: Date?, ) diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt index dbb42a8451..d0c89d9896 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt @@ -27,6 +27,7 @@ internal fun SessionData.toDbModel(): DbSessionData { accessToken = accessToken, refreshToken = refreshToken, homeserverUrl = homeserverUrl, + oidcData = oidcData, slidingSyncProxy = slidingSyncProxy, loginTimestamp = loginTimestamp?.time, ) @@ -39,6 +40,7 @@ internal fun DbSessionData.toApiModel(): SessionData { accessToken = accessToken, refreshToken = refreshToken, homeserverUrl = homeserverUrl, + oidcData = oidcData, slidingSyncProxy = slidingSyncProxy, loginTimestamp = loginTimestamp?.let { Date(it) } ) diff --git a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq index c3123f2ffb..f1dfc51b71 100644 --- a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq +++ b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq @@ -5,7 +5,8 @@ CREATE TABLE SessionData ( refreshToken TEXT, homeserverUrl TEXT NOT NULL, slidingSyncProxy TEXT, - loginTimestamp INTEGER + loginTimestamp INTEGER, + oidcData TEXT ); diff --git a/libraries/session-storage/impl/src/main/sqldelight/migrations/2.sqm b/libraries/session-storage/impl/src/main/sqldelight/migrations/2.sqm new file mode 100644 index 0000000000..9fc7f2fdaa --- /dev/null +++ b/libraries/session-storage/impl/src/main/sqldelight/migrations/2.sqm @@ -0,0 +1 @@ +ALTER TABLE SessionData ADD COLUMN oidcData TEXT; diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt index fc24c5a011..57c911c0b2 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt @@ -37,6 +37,7 @@ class DatabaseSessionStoreTests { homeserverUrl = "homeserverUrl", slidingSyncProxy = null, loginTimestamp = null, + oidcData = "aOidcData", ) @Before From 79aa128377f6c43f9bc4737c15eb38b10b1528f1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 12:54:22 +0200 Subject: [PATCH 10/26] Add setting entry point to manage account (OIDC) --- .../messages/impl/src/main/res/values/localazy.xml | 2 ++ .../preferences/impl/root/PreferencesRootNode.kt | 9 ++++++++- .../impl/root/PreferencesRootPresenter.kt | 13 +++++++++++++ .../preferences/impl/root/PreferencesRootState.kt | 1 + .../impl/root/PreferencesRootStateProvider.kt | 1 + .../preferences/impl/root/PreferencesRootView.kt | 10 ++++++++++ .../impl/root/PreferencesRootPresenterTest.kt | 1 + .../android/libraries/matrix/api/MatrixClient.kt | 1 + .../libraries/matrix/impl/RustMatrixClient.kt | 5 +++++ .../libraries/matrix/test/FakeMatrixClient.kt | 4 ++++ .../ui-strings/src/main/res/values/localazy.xml | 11 +++++++++++ 11 files changed, 57 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index c88beff81a..105cb1fc7b 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -26,11 +26,13 @@ "You can change it in your %1$s." "global settings" "Default setting" + "Remove custom setting" "An error occurred while loading notification settings." "Failed restoring the default mode, please try again." "Failed setting the mode, please try again." "All messages" "Mentions and Keywords only" + "In this room, notify me for" "Show less" "Show more" "Send again" diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index 3f897caf03..e90569b40e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -66,6 +66,12 @@ class PreferencesRootNode @AssistedInject constructor( plugins().forEach { it.onOpenAbout() } } + private fun onManageAccountClicked(activity: Activity, accountManagementUrl: String?) { + accountManagementUrl?.let { + activity.openUrlInChromeCustomTab(null, false, it) + } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -79,7 +85,8 @@ class PreferencesRootNode @AssistedInject constructor( onOpenAbout = this::onOpenAbout, onVerifyClicked = this::onVerifyClicked, onOpenDeveloperSettings = this::onOpenDeveloperSettings, - onSuccessLogout = { onSuccessLogout(activity, it) } + onSuccessLogout = { onSuccessLogout(activity, it) }, + onManageAccountClicked = { onManageAccountClicked(activity, state.accountManagementUrl) }, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index 0cd2e7f7db..b33940b21e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -68,6 +68,14 @@ class PreferencesRootPresenter @Inject constructor( derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified } } + val accountManagementUrl: MutableState = remember { + mutableStateOf(null) + } + + LaunchedEffect(Unit) { + initAccountManagementUrl(accountManagementUrl) + } + val logoutState = logoutPresenter.present() val showDeveloperSettings = buildType != BuildType.RELEASE return PreferencesRootState( @@ -75,6 +83,7 @@ class PreferencesRootPresenter @Inject constructor( myUser = matrixUser.value, version = versionFormatter.get(), showCompleteVerification = sessionIsNotVerified, + accountManagementUrl = accountManagementUrl.value, showAnalyticsSettings = hasAnalyticsProviders, showDeveloperSettings = showDeveloperSettings, snackbarMessage = snackbarMessage, @@ -84,4 +93,8 @@ class PreferencesRootPresenter @Inject constructor( private fun CoroutineScope.initialLoad(matrixUser: MutableState) = launch { matrixUser.value = matrixClient.getCurrentUser() } + + private fun CoroutineScope.initAccountManagementUrl(accountManagementUrl: MutableState) = launch { + accountManagementUrl.value = matrixClient.getAccountManagementUrl().getOrNull() + } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt index 540c470815..af3a090630 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt @@ -25,6 +25,7 @@ data class PreferencesRootState( val myUser: MatrixUser?, val version: String, val showCompleteVerification: Boolean, + val accountManagementUrl: String?, val showAnalyticsSettings: Boolean, val showDeveloperSettings: Boolean, val snackbarMessage: SnackbarMessage?, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index e8c148267f..931a560c1d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt @@ -25,6 +25,7 @@ fun aPreferencesRootState() = PreferencesRootState( myUser = null, version = "Version 1.1 (1)", showCompleteVerification = true, + accountManagementUrl = "aUrl", showAnalyticsSettings = true, showDeveloperSettings = true, snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete), diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 1cb3a37c77..c24a2ec875 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -23,6 +23,7 @@ import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.DeveloperMode import androidx.compose.material.icons.outlined.Help import androidx.compose.material.icons.outlined.InsertChart +import androidx.compose.material.icons.outlined.ManageAccounts import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -51,6 +52,7 @@ fun PreferencesRootView( state: PreferencesRootState, onBackPressed: () -> Unit, onVerifyClicked: () -> Unit, + onManageAccountClicked: () -> Unit, onOpenAnalytics: () -> Unit, onOpenRageShake: () -> Unit, onOpenAbout: () -> Unit, @@ -76,6 +78,13 @@ fun PreferencesRootView( ) HorizontalDivider() } + if (state.accountManagementUrl != null) { + PreferenceText( + title = stringResource(id = CommonStrings.screen_settings_oidc_account), + icon = Icons.Outlined.ManageAccounts, + onClick = onManageAccountClicked, + ) + } if (state.showAnalyticsSettings) { PreferenceText( title = stringResource(id = CommonStrings.common_analytics), @@ -143,5 +152,6 @@ private fun ContentToPreview(matrixUser: MatrixUser) { onOpenAbout = {}, onVerifyClicked = {}, onSuccessLogout = {}, + onManageAccountClicked = {}, ) } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 580426fcfa..5d508912a8 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -64,6 +64,7 @@ class PreferencesRootPresenterTest { ) assertThat(loadedState.showDeveloperSettings).isEqualTo(true) assertThat(loadedState.showAnalyticsSettings).isEqualTo(false) + assertThat(loadedState.accountManagementUrl).isNull() } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 705cbbc620..be64a3371f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -63,6 +63,7 @@ interface MatrixClient : Closeable { suspend fun logout(): String? suspend fun loadUserDisplayName(): Result suspend fun loadUserAvatarURLString(): Result + suspend fun getAccountManagementUrl(): Result suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result fun roomMembershipObserver(): RoomMembershipObserver diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index e7b1ad4d5e..58d82ec116 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -311,6 +311,11 @@ class RustMatrixClient constructor( return result } + override suspend fun getAccountManagementUrl(): Result = withContext(sessionDispatcher) { + runCatching { + client.accountUrl() + } + } override suspend fun loadUserDisplayName(): Result = withContext(sessionDispatcher) { runCatching { client.displayName() diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 1734aec718..83f7f3ad79 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -51,6 +51,7 @@ class FakeMatrixClient( private val pushersService: FakePushersService = FakePushersService(), private val notificationService: FakeNotificationService = FakeNotificationService(), private val syncService: FakeSyncService = FakeSyncService(), + private val accountManagementUrlString: Result = Result.success(null), ) : MatrixClient { private var ignoreUserResult: Result = Result.success(Unit) @@ -125,6 +126,9 @@ class FakeMatrixClient( return userAvatarURLString } + override suspend fun getAccountManagementUrl(): Result { + return accountManagementUrlString + } override suspend fun uploadMedia( mimeType: String, data: ByteArray, diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 3b4c305ffc..e38c4fc6b8 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -180,11 +180,21 @@ "Setting up your account." "Additional settings" "Audio and video calls" + "Configuration mismatch" + "We’ve simplified Notifications Settings to make options easier to find. + +Some custom settings you’ve chosen in the past are not shown here, but they’re still active. + +If you proceed, some of your settings may change." "Direct chats" + "Custom setting per chat" "An error occurred while updating the notification setting." + "All messages" + "Mentions and Keywords only" "On direct chats, notify me for" "On group chats, notify me for" "Enable notifications on this device" + "The configuration has not been corrected, please try again." "Group chats" "Mentions" "All" @@ -196,6 +206,7 @@ "System notifications turned off" "Notifications" "Check if you want to hide all current and future messages from this user" + "Account and devices" "Share location" "Share my location" "Open in Apple Maps" From 3f8e7eaef8941ba5726048f5feb922dec0a674a3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 13:12:11 +0200 Subject: [PATCH 11/26] Add missing tests. --- .../ConfirmAccountProviderPresenterTest.kt | 120 ++++++++++++++++++ .../tests/testutils/WaitingForAssertion.kt | 31 +++++ 2 files changed, 151 insertions(+) create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt 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 73f98a2667..95cd9bf053 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 @@ -20,6 +20,7 @@ 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.features.login.api.oidc.OidcAction import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow @@ -30,6 +31,7 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.waitForPredicate import kotlinx.coroutines.test.runTest import org.junit.Test @@ -92,6 +94,124 @@ class ConfirmAccountProviderPresenterTest { } } + @Test + fun `present - oidc - cancel with failure`() = runTest { + val authenticationService = FakeAuthenticationService() + val defaultOidcActionFlow = DefaultOidcActionFlow() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + ) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) + val loadingState = awaitItem() + assertThat(loadingState.submitEnabled).isTrue() + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.submitEnabled).isFalse() + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) + authenticationService.givenOidcCancelError(A_THROWABLE) + defaultOidcActionFlow.post(OidcAction.GoBack) + val cancelFailureState = awaitItem() + assertThat(cancelFailureState.loginFlow).isInstanceOf(Async.Failure::class.java) + } + } + + @Test + fun `present - oidc - cancel with success`() = runTest { + val authenticationService = FakeAuthenticationService() + val defaultOidcActionFlow = DefaultOidcActionFlow() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + ) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) + val loadingState = awaitItem() + assertThat(loadingState.submitEnabled).isTrue() + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.submitEnabled).isFalse() + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) + defaultOidcActionFlow.post(OidcAction.GoBack) + val cancelFinalState = awaitItem() + assertThat(cancelFinalState.loginFlow).isInstanceOf(Async.Uninitialized::class.java) + } + } + + @Test + fun `present - oidc - success with failure`() = runTest { + val authenticationService = FakeAuthenticationService() + val defaultOidcActionFlow = DefaultOidcActionFlow() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + ) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) + val loadingState = awaitItem() + assertThat(loadingState.submitEnabled).isTrue() + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.submitEnabled).isFalse() + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) + authenticationService.givenLoginError(A_THROWABLE) + defaultOidcActionFlow.post(OidcAction.Success("aUrl")) + val cancelLoadingState = awaitItem() + assertThat(cancelLoadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val cancelFailureState = awaitItem() + assertThat(cancelFailureState.loginFlow).isInstanceOf(Async.Failure::class.java) + } + } + + @Test + fun `present - oidc - success with success`() = runTest { + val authenticationService = FakeAuthenticationService() + val defaultOidcActionFlow = DefaultOidcActionFlow() + val defaultLoginUserStory = DefaultLoginUserStory().apply { + setLoginFlowIsDone(false) + } + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + defaultLoginUserStory = defaultLoginUserStory, + ) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) + val loadingState = awaitItem() + assertThat(loadingState.submitEnabled).isTrue() + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.submitEnabled).isFalse() + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) + assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse() + defaultOidcActionFlow.post(OidcAction.Success("aUrl")) + val successSuccessState = awaitItem() + assertThat(successSuccessState.loginFlow).isInstanceOf(Async.Loading::class.java) + waitForPredicate { defaultLoginUserStory.loginFlowIsDone.value } + } + } + @Test fun `present - submit fails`() = runTest { val authenticationService = FakeAuthenticationService() diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt new file mode 100644 index 0000000000..6818aafc5b --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.tests.testutils + +import kotlinx.coroutines.delay + +suspend fun waitForPredicate( + delayBetweenAttemptsMillis: Long = 1, + maxNumberOfAttempts: Int = 20, + predicate: () -> Boolean, +) { + for (i in 0..maxNumberOfAttempts) { + if (predicate()) return + if (i < maxNumberOfAttempts) delay(delayBetweenAttemptsMillis) + } + throw AssertionError("Predicate was not true after $maxNumberOfAttempts attempts") +} From 10e8517766ef226520df9a623dc7edd4a3a5c4d2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 14:42:46 +0200 Subject: [PATCH 12/26] Implement didRefreshTokens(): update database with updated SessionData. --- .../libraries/matrix/impl/RustMatrixClient.kt | 5 ++- .../auth/RustMatrixAuthenticationService.kt | 15 +------ .../libraries/matrix/impl/mapper/Session.kt | 32 +++++++++++++++ .../sessionstorage/api/SessionStore.kt | 6 +++ .../impl/memory/InMemorySessionStore.kt | 4 ++ .../impl/DatabaseSessionStore.kt | 18 ++++++++ .../libraries/matrix/session/SessionData.sq | 3 ++ .../impl/DatabaseSessionStoreTests.kt | 41 +++++++++++++++++++ 8 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 58d82ec116..c68913b705 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.impl.core.toProgressWatcher +import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.matrix.impl.media.RustMediaLoader import io.element.android.libraries.matrix.impl.notification.RustNotificationService import io.element.android.libraries.matrix.impl.pushers.RustPushersService @@ -122,7 +123,9 @@ class RustMatrixClient constructor( override fun didRefreshTokens() { Timber.w("didRefreshTokens()") - // TODO handle refresh token + appCoroutineScope.launch { + sessionStore.updateData(client.session().toSessionData()) + } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index fe6bf7dea9..6014644733 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -28,18 +28,16 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.impl.RustMatrixClientFactory import io.element.android.libraries.matrix.impl.exception.mapClientException +import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.network.useragent.UserAgentProvider -import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.OidcAuthenticationData -import org.matrix.rustcomponents.sdk.Session import org.matrix.rustcomponents.sdk.use import java.io.File -import java.util.Date import javax.inject.Inject import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService @@ -155,14 +153,3 @@ class RustMatrixAuthenticationService @Inject constructor( } } } - -private fun Session.toSessionData() = SessionData( - userId = userId, - deviceId = deviceId, - accessToken = accessToken, - refreshToken = refreshToken, - homeserverUrl = homeserverUrl, - oidcData = oidcData, - slidingSyncProxy = slidingSyncProxy, - loginTimestamp = Date(), -) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt new file mode 100644 index 0000000000..825c6f4397 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.mapper + +import io.element.android.libraries.sessionstorage.api.SessionData +import org.matrix.rustcomponents.sdk.Session +import java.util.Date + +internal fun Session.toSessionData() = SessionData( + userId = userId, + deviceId = deviceId, + accessToken = accessToken, + refreshToken = refreshToken, + homeserverUrl = homeserverUrl, + oidcData = oidcData, + slidingSyncProxy = slidingSyncProxy, + loginTimestamp = Date(), +) diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt index d79d700030..2b3398f76c 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt @@ -23,6 +23,12 @@ interface SessionStore { fun isLoggedIn(): Flow fun sessionsFlow(): Flow> suspend fun storeData(sessionData: SessionData) + + /** + * Will update the session data matching the userId, except the value of loginTimestamp. + * No op if userId is not found in DB. + */ + suspend fun updateData(sessionData: SessionData) suspend fun getSession(sessionId: String): SessionData? suspend fun getAllSessions(): List suspend fun getLatestSession(): SessionData? diff --git a/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt b/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt index e23e34983c..df78149eef 100644 --- a/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt +++ b/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt @@ -38,6 +38,10 @@ class InMemorySessionStore : SessionStore { sessionDataFlow.value = sessionData } + override suspend fun updateData(sessionData: SessionData) { + sessionDataFlow.value = sessionData + } + override suspend fun getSession(sessionId: String): SessionData? { return sessionDataFlow.value.takeIf { it?.userId == sessionId } } diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt index 9394b66e66..eb273411a0 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt @@ -46,6 +46,24 @@ class DatabaseSessionStore @Inject constructor( database.sessionDataQueries.insertSessionData(sessionData.toDbModel()) } + override suspend fun updateData(sessionData: SessionData) { + val result = database.sessionDataQueries.selectByUserId(sessionData.userId) + .executeAsOneOrNull() + ?.toApiModel() + + if (result == null) { + Timber.e("User ${sessionData.userId} not found in session database") + return + } + + // Copy new data from SDK, but keep login timestamp + database.sessionDataQueries.updateSession( + sessionData.copy( + loginTimestamp = result.loginTimestamp, + ).toDbModel() + ) + } + override suspend fun getLatestSession(): SessionData? { return database.sessionDataQueries.selectFirst() .executeAsOneOrNull() diff --git a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq index f1dfc51b71..05049c5635 100644 --- a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq +++ b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq @@ -24,3 +24,6 @@ INSERT INTO SessionData VALUES ?; removeSession: DELETE FROM SessionData WHERE userId = ?; + +updateSession: +REPLACE INTO SessionData VALUES ?; diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt index 57c911c0b2..e035ff9ae1 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt @@ -109,4 +109,45 @@ class DatabaseSessionStoreTests { assertThat(database.sessionDataQueries.selectByUserId(aSessionData.userId).executeAsOneOrNull()).isNull() } + + @Test + fun `update session update all fields except loginTimestamp`() = runTest { + val firstSessionData = SessionData( + userId = "userId", + deviceId = "deviceId", + accessToken = "accessToken", + refreshToken = "refreshToken", + homeserverUrl = "homeserverUrl", + slidingSyncProxy = "slidingSyncProxy", + loginTimestamp = 1, + oidcData = "aOidcData", + ) + val secondSessionData = SessionData( + userId = "userId", + deviceId = "deviceIdAltered", + accessToken = "accessTokenAltered", + refreshToken = "refreshTokenAltered", + homeserverUrl = "homeserverUrlAltered", + slidingSyncProxy = "slidingSyncProxyAltered", + loginTimestamp = 2, + oidcData = "aOidcDataAltered", + ) + assertThat(firstSessionData.userId).isEqualTo(secondSessionData.userId) + assertThat(firstSessionData.loginTimestamp).isNotEqualTo(secondSessionData.loginTimestamp) + + database.sessionDataQueries.insertSessionData(firstSessionData) + databaseSessionStore.updateData(secondSessionData.toApiModel()) + + // Get the altered session + val alteredSession = databaseSessionStore.getSession(firstSessionData.userId)!!.toDbModel() + + assertThat(alteredSession.userId).isEqualTo(secondSessionData.userId) + assertThat(alteredSession.deviceId).isEqualTo(secondSessionData.deviceId) + assertThat(alteredSession.accessToken).isEqualTo(secondSessionData.accessToken) + assertThat(alteredSession.refreshToken).isEqualTo(secondSessionData.refreshToken) + assertThat(alteredSession.homeserverUrl).isEqualTo(secondSessionData.homeserverUrl) + assertThat(alteredSession.slidingSyncProxy).isEqualTo(secondSessionData.slidingSyncProxy) + assertThat(alteredSession.loginTimestamp).isEqualTo(/* Not altered! */ firstSessionData.loginTimestamp) + assertThat(alteredSession.oidcData).isEqualTo(secondSessionData.oidcData) + } } From 80aaa8b8ab88ebfa6a44f2249c6c4c606ebea29f Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 23 Aug 2023 14:50:22 +0200 Subject: [PATCH 13/26] When event has no id, just cancel parsing the latest room message (#1126) --- changelog.d/1125.bugfix | 1 + .../libraries/matrix/impl/room/message/RoomMessageFactory.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1125.bugfix diff --git a/changelog.d/1125.bugfix b/changelog.d/1125.bugfix new file mode 100644 index 0000000000..85ebb87033 --- /dev/null +++ b/changelog.d/1125.bugfix @@ -0,0 +1 @@ +When event has no id, just cancel parsing the latest room message for a room. diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/RoomMessageFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/RoomMessageFactory.kt index 3c298b5ec6..6f46f73351 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/RoomMessageFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/RoomMessageFactory.kt @@ -25,7 +25,7 @@ class RoomMessageFactory { eventTimelineItem ?: return null val mappedTimelineItem = EventTimelineItemMapper().map(eventTimelineItem) return RoomMessage( - eventId = mappedTimelineItem.eventId!!, + eventId = mappedTimelineItem.eventId ?: return null, event = mappedTimelineItem, sender = mappedTimelineItem.sender, originServerTs = mappedTimelineItem.timestamp, From d9ff14d9bce0df584a7fc84f2960cfe7843d7dc4 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 23 Aug 2023 13:28:17 +0000 Subject: [PATCH 14/26] Update screenshots --- ...ll_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ll_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...l_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...l_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png index 9202a41eb0..d590c4eb8e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a80a93fd971e46b3a62c5e2679bfdab30cc01589398842346a2c7815af299fe -size 35413 +oid sha256:72eee76cc8244eb54f147fc589c7b200dc3a46db4ea7306dbd6757918e4fffde +size 39744 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png index 7def318962..a3449da98e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49d6cc10dee437df8428616173de9b94b1b80c4b9edc72057e64b6dd51fa608d -size 34701 +oid sha256:8796e5f70cdd09087ed22ede78c3aed985dcd57e073f68d83dd5884e4f29a2c8 +size 39042 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png index 3eb39d8862..370689a2fc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:522926ef7065fab0176b686e49cf27ad29d4fd44d18d462cb156d45332cf368d -size 37511 +oid sha256:97250f48dfa0cf2320f837eb87ad90d8a1e73642fbb2d571f326d689c4fc10e0 +size 42373 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png index 914485e483..6bf7f8fdb0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6aaddaf0c8b536ddc231c74b6bdcb22114fca8e7445e97af91c064333873af2c -size 37614 +oid sha256:73f2811197c014d91834d39dad1fe18abdaea9b2510d8deccead39c10a1b5aa8 +size 42473 From 281df6aec59383cd3e5fe5abf8f20e7346bc5f48 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 16:04:55 +0200 Subject: [PATCH 15/26] Changelog. --- changelog.d/1127.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1127.feature diff --git a/changelog.d/1127.feature b/changelog.d/1127.feature new file mode 100644 index 0000000000..da2c6b3cf1 --- /dev/null +++ b/changelog.d/1127.feature @@ -0,0 +1 @@ +Enable OIDC support. From bd8585e6d228492af078dba33d3cba2c66a2a925 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:33:52 +0000 Subject: [PATCH 16/26] Update dependency androidx.compose.material3:material3 to v1.2.0-alpha06 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d664e32530..2feba83d4a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -92,7 +92,7 @@ androidx_startup = { module = "androidx.startup:startup-runtime", version.ref = androidx_preference = "androidx.preference:preference:1.2.1" androidx_compose_bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose_bom" } -androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha05" +androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha06" # Coroutines coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } From a7a36a94354ac1a69469555a9323f1fac837b817 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 24 Aug 2023 06:26:31 +0000 Subject: [PATCH 17/26] Update screenshots --- ...mpl_null_OnBoardingScreen-D-0_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...mpl_null_OnBoardingScreen-D-0_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...mpl_null_OnBoardingScreen-N-0_1_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...mpl_null_OnBoardingScreen-N-0_1_null_3,NEXUS_5,1.0,en].png | 2 +- ...l_IconTitleSubtitleMoleculeDark_0_null,NEXUS_5,1.0,en].png | 4 ++-- ..._IconTitleSubtitleMoleculeLight_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...gnsystem.text_null_DpScale_1_5f_0_null,NEXUS_5,1.0,en].png | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_1,NEXUS_5,1.0,en].png index a40fc3b88b..408bee4a7d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c0cac586f62b725e9377111e3c2d39001048aebfd1f8fd113ea3e55d16d781c -size 316907 +oid sha256:6e902df5e96079c03fbbd36b79791c90f637ab29f4b2a2aa6df7981e09d93ee0 +size 316906 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_3,NEXUS_5,1.0,en].png index 8870669181..90e7db4d77 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-D-0_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a8aab4a7fc06146668156c43424446d03ef8efb1c0ce1fd2105d45ced3e96d9 -size 310355 +oid sha256:4de11f9a3d8a0165607960b926a2c1b14b6d0561ae52cbf504543bf0692fae09 +size 310356 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_1,NEXUS_5,1.0,en].png index 3d86da9aeb..114dcd3510 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b571751a70c761f4a460e0b486bd519faf30b08ec9ac208939d6690bc115874 -size 405239 +oid sha256:0630d924f2bd374f8bd53ad41344b8d66ead984ce4bc0b55d455752d8d5409d9 +size 405238 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_3,NEXUS_5,1.0,en].png index ad5b2e6482..1d39d2027d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.onboarding.impl_null_OnBoardingScreen-N-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0d500141b3cdf3410fddce876ee4c68d8ece26e40e34bd094cf95a7985ff199 +oid sha256:77f91c56ce019ea31d618e63a5cba1c09d43140fd8b92458de2477e95a70ea7f size 392950 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMoleculeDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMoleculeDark_0_null,NEXUS_5,1.0,en].png index 77da1e50e6..8a85ee36d5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMoleculeDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMoleculeDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02627b5036d3d937142e0086f5a50871fc760b787416e23f8b54e046c377060d -size 10251 +oid sha256:abe1134e30761c5ec6815c27568f0238b12f42c32746c69e428be477f28cc203 +size 10233 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMoleculeLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMoleculeLight_0_null,NEXUS_5,1.0,en].png index 2bdf663d41..4dfeab877c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMoleculeLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMoleculeLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba3088e305d112ab0265c5d18005556c522f0b705bd2c547c23ebdd0b25c04ca -size 9849 +oid sha256:162a11f98fc16c2cf2de9380594bb29893c067479f5c9b2405b66b58474ae8c7 +size 9850 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.text_null_DpScale_1_5f_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.text_null_DpScale_1_5f_0_null,NEXUS_5,1.0,en].png index 604707fd71..6f7eca2dec 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.text_null_DpScale_1_5f_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.text_null_DpScale_1_5f_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f49f70bf1c036ad801002094642773cb569025d2d2eedb27a462dd3405dc0262 -size 28348 +oid sha256:c6ac3ab7ac24942fca51adfc3cc8bf5ad797e912baf835baa73e8289b6d4a94d +size 28308 From f9057029cfa5dde26d017472e19f92df3d4ab784 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 06:43:05 +0000 Subject: [PATCH 18/26] Update dependency org.matrix.rustcomponents:sdk-android to v0.1.46 (#1130) * Update dependency org.matrix.rustcomponents:sdk-android to v0.1.46 * Remove poll end code. The poll end event isn't shown in the timeline anymore. --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Marco Romano --- .../event/TimelineItemContentFactory.kt | 3 -- .../TimelineItemContentPollEndFactory.kt | 29 ------------------- .../impl/timeline/groups/Groupability.kt | 2 -- .../messages/fixtures/timelineItemsFactory.kt | 2 -- gradle/libs.versions.toml | 2 +- .../impl/DefaultRoomLastMessageFormatter.kt | 3 +- .../impl/DefaultTimelineEventFormatter.kt | 2 -- .../api/timeline/item/event/EventContent.kt | 4 --- .../item/event/TimelineEventContentMapper.kt | 4 --- 9 files changed, 2 insertions(+), 49 deletions(-) delete mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollEndFactory.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index b3b2c896c3..1de4ee7c86 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent @@ -38,7 +37,6 @@ class TimelineItemContentFactory @Inject constructor( private val redactedMessageFactory: TimelineItemContentRedactedFactory, private val stickerFactory: TimelineItemContentStickerFactory, private val pollFactory: TimelineItemContentPollFactory, - private val pollEndFactory: TimelineItemContentPollEndFactory, private val utdFactory: TimelineItemContentUTDFactory, private val roomMembershipFactory: TimelineItemContentRoomMembershipFactory, private val profileChangeFactory: TimelineItemContentProfileChangeFactory, @@ -58,7 +56,6 @@ class TimelineItemContentFactory @Inject constructor( is StateContent -> stateFactory.create(eventTimelineItem) is StickerContent -> stickerFactory.create(itemContent) is PollContent -> pollFactory.create(itemContent) - is PollEndContent -> pollEndFactory.create(itemContent) is UnableToDecryptContent -> utdFactory.create(itemContent) is UnknownContent -> TimelineItemUnknownContent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollEndFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollEndFactory.kt deleted file mode 100644 index ff9eb837b6..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollEndFactory.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.messages.impl.timeline.factories.event - -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent -import javax.inject.Inject - -class TimelineItemContentPollEndFactory @Inject constructor() { - - fun create(@Suppress("UNUSED_PARAMETER") content: PollEndContent): TimelineItemEventContent { - return TimelineItemUnknownContent - } -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt index 1d2dec09b7..844942002a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt @@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent @@ -79,7 +78,6 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean { RedactedContent, is StickerContent, is PollContent, - is PollEndContent, is UnableToDecryptContent -> true is FailedToParseStateContent, is ProfileChangeContent, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt index 8fea1ae155..0d4dc98340 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt @@ -21,7 +21,6 @@ import io.element.android.features.messages.impl.timeline.factories.event.Timeli import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseMessageFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseStateFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentMessageFactory -import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentPollEndFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentPollFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentProfileChangeFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentRedactedFactory @@ -54,7 +53,6 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory { redactedMessageFactory = TimelineItemContentRedactedFactory(), stickerFactory = TimelineItemContentStickerFactory(), pollFactory = TimelineItemContentPollFactory(matrixClient, FakeFeatureFlagService()), - pollEndFactory = TimelineItemContentPollEndFactory(), utdFactory = TimelineItemContentUTDFactory(), roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter), profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d664e32530..ca3d9203a0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -146,7 +146,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.45" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.46" sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt index 0736cf61ce..65fc191e53 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt @@ -38,7 +38,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageConten import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent @@ -96,7 +95,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( is StateContent -> { stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.RoomList) } - is PollContent, is PollEndContent, // TODO Polls: handle last message + is PollContent, is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> { prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisplayName, isDmRoom) } diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt index 42d8aae083..2b729fb0d5 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt @@ -27,7 +27,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent @@ -66,7 +65,6 @@ class DefaultTimelineEventFormatter @Inject constructor( RedactedContent, is StickerContent, is PollContent, - is PollEndContent, is UnableToDecryptContent, is MessageContent, is FailedToParseMessageLikeContent, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index 82c322668c..3316de64eb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -80,10 +80,6 @@ data class PollContent( val endTime: ULong? ) : EventContent -data class PollEndContent( - val startEventId: String -) : EventContent - data class UnableToDecryptContent( val data: Data ) : EventContent { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 7ee1d1490d..22f8a062f0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange import io.element.android.libraries.matrix.api.timeline.item.event.OtherState import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent @@ -106,9 +105,6 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap endTime = kind.endTime, ) } - is TimelineItemContentKind.PollEnd -> { - PollEndContent(startEventId = kind.startEventId) - } is TimelineItemContentKind.UnableToDecrypt -> { UnableToDecryptContent( data = kind.msg.map() From 6457f3c4f5a9f1d429395dc8a9c7d32bea18948c Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 24 Aug 2023 09:41:58 +0200 Subject: [PATCH 19/26] Fix sent videos being cropped (#1124) --- changelog.d/862.bugfix | 1 + .../android/libraries/mediaupload/VideoCompressor.kt | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 changelog.d/862.bugfix diff --git a/changelog.d/862.bugfix b/changelog.d/862.bugfix new file mode 100644 index 0000000000..30715a76e3 --- /dev/null +++ b/changelog.d/862.bugfix @@ -0,0 +1 @@ +Videos sent from the app were cropped in some cases. diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt index e7e294cd7c..5587eedfa4 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt @@ -20,6 +20,8 @@ import android.content.Context import android.net.Uri import com.otaliastudios.transcoder.Transcoder import com.otaliastudios.transcoder.TranscoderListener +import com.otaliastudios.transcoder.resize.AtMostResizer +import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy import io.element.android.libraries.androidutils.file.createTmpFile import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.di.ApplicationContext @@ -35,6 +37,11 @@ class VideoCompressor @Inject constructor( fun compress(uri: Uri) = callbackFlow { val tmpFile = context.createTmpFile(extension = "mp4") val future = Transcoder.into(tmpFile.path) + .setVideoTrackStrategy( + DefaultVideoStrategy.Builder() + .addResizer(AtMostResizer(1920, 1080)) + .build() + ) .addDataSource(context, uri) .setListener(object : TranscoderListener { override fun onTranscodeProgress(progress: Double) { From c41db7daa471a0573338fd269da520aaa4626a02 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 09:54:14 +0200 Subject: [PATCH 20/26] Move the file pull_request_template.md to the parent folder to make it the default template. --- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md From 6b96a3adf2c4cabdc3ba88e68b2010ea80c40da0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 09:55:29 +0200 Subject: [PATCH 21/26] Element X --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d83826f9f1..11e6aeba19 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to Element Android +# Contributing to Element X Android From 94007bac941c169e547374efe9b73df509799494 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 10:07:10 +0200 Subject: [PATCH 22/26] Update pull request template for Element X. --- .github/pull_request_template.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 431c018fdd..aec678857c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ - + ## Type of change @@ -17,13 +17,17 @@ ## Screenshots / GIFs - - -- [ ] Changes has been tested on an Android device or Android emulator with API 21 +- [ ] Changes has been tested on an Android device or Android emulator with API 23 - [ ] UI change has been tested on both light and dark themes -- [ ] Accessibility has been taken into account. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#accessibility +- [ ] Accessibility has been taken into account. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#accessibility - [ ] Pull request is based on the develop branch -- [ ] Pull request includes a new file under ./changelog.d. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog +- [ ] Pull request includes a new file under ./changelog.d. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#changelog - [ ] Pull request includes screenshots or videos if containing UI changes - [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off) - [ ] You've made a self review of your PR From 5c6ecd832756f2eccdc9787d8e73208b01506bb1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 11:15:37 +0200 Subject: [PATCH 23/26] Fix grammar --- .github/pull_request_template.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index aec678857c..7aabcb77aa 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,9 +18,9 @@ ## Screenshots / GIFs -- [ ] Changes has been tested on an Android device or Android emulator with API 23 +- [ ] Changes have been tested on an Android device or Android emulator with API 23 - [ ] UI change has been tested on both light and dark themes - [ ] Accessibility has been taken into account. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#accessibility - [ ] Pull request is based on the develop branch From f5fd505bc27df633f423ae4ef2e4d1be976ab6f4 Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Thu, 24 Aug 2023 11:54:01 +0200 Subject: [PATCH 24/26] Surface send poll start API from rust sdk (#1140) Rust api added in: https://github.com/matrix-org/matrix-rust-sdk/pull/2391 Closes https://github.com/vector-im/element-meta/issues/2010 --- .../libraries/matrix/api/room/MatrixRoom.kt | 16 ++++++++++++ .../libraries/matrix/impl/poll/PollKind.kt | 5 ++++ .../matrix/impl/room/RustMatrixRoom.kt | 21 ++++++++++++++- .../matrix/test/room/FakeMatrixRoom.kt | 26 +++++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index f2c51c357e..88d35c83d4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import kotlinx.coroutines.flow.StateFlow @@ -141,6 +142,21 @@ interface MatrixRoom : Closeable { assetType: AssetType? = null, ): Result + /** + * Create a poll in the room. + * + * @param question The question to ask. + * @param answers The list of answers. + * @param maxSelections The maximum number of answers that can be selected. + * @param pollKind The kind of poll to create. + */ + suspend fun createPoll( + question: String, + answers: List, + maxSelections: Int, + pollKind: PollKind, + ): Result + override fun close() = destroy() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt index bde49464ad..d0682edf2c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt @@ -23,3 +23,8 @@ fun RustPollKind.map(): PollKind = when (this) { RustPollKind.DISCLOSED -> PollKind.Disclosed RustPollKind.UNDISCLOSED -> PollKind.Undisclosed } + +fun PollKind.toInner(): RustPollKind = when (this) { + PollKind.Disclosed -> RustPollKind.DISCLOSED + PollKind.Undisclosed -> RustPollKind.UNDISCLOSED +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 878aaa4473..ffe06379d3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType @@ -41,6 +42,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventType import io.element.android.libraries.matrix.impl.core.toProgressWatcher import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl import io.element.android.libraries.matrix.impl.media.map +import io.element.android.libraries.matrix.impl.poll.toInner import io.element.android.libraries.matrix.impl.room.location.toInner import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import io.element.android.libraries.matrix.impl.util.destroyAll @@ -378,7 +380,24 @@ class RustMatrixRoom( description = description, zoomLevel = zoomLevel?.toUByte(), assetType = assetType?.toInner(), - txnId = genTransactionId() + txnId = genTransactionId(), + ) + } + } + + override suspend fun createPoll( + question: String, + answers: List, + maxSelections: Int, + pollKind: PollKind, + ): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.createPoll( + question = question, + answers = answers, + maxSelections = maxSelections.toUByte(), + pollKind = pollKind.toInner(), + txnId = genTransactionId(), ) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index a660c56a99..88f705162e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType @@ -83,6 +84,7 @@ class FakeMatrixRoom( private var forwardEventResult = Result.success(Unit) private var reportContentResult = Result.success(Unit) private var sendLocationResult = Result.success(Unit) + private var createPollResult = Result.success(Unit) private var progressCallbackValues = emptyList>() val editMessageCalls = mutableListOf() @@ -104,6 +106,9 @@ class FakeMatrixRoom( private val _sentLocations = mutableListOf() val sentLocations: List = _sentLocations + private val _createPollInvocations = mutableListOf() + val createPollInvocations: List = _createPollInvocations + var invitedUserId: UserId? = null private set @@ -305,6 +310,16 @@ class FakeMatrixRoom( return sendLocationResult } + override suspend fun createPoll( + question: String, + answers: List, + maxSelections: Int, + pollKind: PollKind + ): Result = simulateLongTask { + _createPollInvocations.add(CreatePollInvocation(question, answers, maxSelections, pollKind)) + return createPollResult + } + fun givenLeaveRoomError(throwable: Throwable?) { this.leaveRoomError = throwable } @@ -397,6 +412,10 @@ class FakeMatrixRoom( sendLocationResult = result } + fun givenCreatePollResult(result: Result) { + createPollResult = result + } + fun givenProgressCallbackValues(values: List>) { progressCallbackValues = values } @@ -409,3 +428,10 @@ data class SendLocationInvocation( val zoomLevel: Int?, val assetType: AssetType?, ) + +data class CreatePollInvocation( + val question: String, + val answers: List, + val maxSelections: Int, + val pollKind: PollKind, +) From 39ef780fe7826802fe3f4012f95d63cdc4097a24 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 24 Aug 2023 13:43:36 +0200 Subject: [PATCH 25/26] Prevent verification while initial sync is in progress (#1138) * Prevent verification while initial sync is in progress * Add `canVerifySessionFlow` to simplify the check --- changelog.d/1131.bugfix | 1 + .../impl/root/PreferencesRootPresenter.kt | 11 +++-------- .../features/roomlist/impl/RoomListPresenter.kt | 5 ++--- .../roomlist/impl/RoomListPresenterTests.kt | 2 +- .../verification/SessionVerificationService.kt | 6 ++++++ .../libraries/matrix/impl/RustMatrixClient.kt | 17 +++++++---------- .../RustSessionVerificationService.kt | 11 ++++++++++- .../FakeSessionVerificationService.kt | 11 ++++++++--- 8 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 changelog.d/1131.bugfix diff --git a/changelog.d/1131.bugfix b/changelog.d/1131.bugfix new file mode 100644 index 0000000000..23649a19d0 --- /dev/null +++ b/changelog.d/1131.bugfix @@ -0,0 +1 @@ +Only display verification prompt after initial sync is done. diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index b33940b21e..af82a3ab2a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -20,7 +20,6 @@ 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 import androidx.compose.runtime.remember @@ -34,7 +33,6 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.getCurrentUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -62,11 +60,8 @@ class PreferencesRootPresenter @Inject constructor( val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() } - // Session verification status (unknown, not verified, verified) - val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState() - val sessionIsNotVerified by remember { - derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified } - } + // We should display the 'complete verification' option if the current session can be verified + val showCompleteVerification by sessionVerificationService.canVerifySessionFlow.collectAsState(false) val accountManagementUrl: MutableState = remember { mutableStateOf(null) @@ -82,7 +77,7 @@ class PreferencesRootPresenter @Inject constructor( logoutState = logoutState, myUser = matrixUser.value, version = versionFormatter.get(), - showCompleteVerification = sessionIsNotVerified, + showCompleteVerification = showCompleteVerification, accountManagementUrl = accountManagementUrl.value, showAnalyticsSettings = hasAnalyticsProviders, showDeveloperSettings = showDeveloperSettings, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index dbef9c799b..e069634d49 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -39,7 +39,6 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.getCurrentUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject @@ -73,11 +72,11 @@ class RoomListPresenter @Inject constructor( } // Session verification status (unknown, not verified, verified) - val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState() + val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false) var verificationPromptDismissed by rememberSaveable { mutableStateOf(false) } // We combine both values to only display the prompt if the session is not verified and it wasn't dismissed val displayVerificationPrompt by remember { - derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified && !verificationPromptDismissed } + derivedStateOf { canVerifySession && !verificationPromptDismissed } } var displaySearchResults by rememberSaveable { mutableStateOf(false) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index eaa3801e12..d8524d5834 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -202,7 +202,7 @@ class RoomListPresenterTests { fun `present - handle DismissRequestVerificationPrompt`() = runTest { val roomListService = FakeRoomListService() val matrixClient = FakeMatrixClient( - roomListService = roomListService + roomListService = roomListService, ) val presenter = createRoomListPresenter( client = matrixClient, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index b2f79c0750..4cb1918ce1 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.api.verification +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface SessionVerificationService { @@ -37,6 +38,11 @@ interface SessionVerificationService { */ val sessionVerifiedStatus: StateFlow + /** + * Returns whether the current session needs to be verified and the SDK is ready to start the verification. + */ + val canVerifySessionFlow: Flow + /** * Request verification of the current session. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index c68913b705..4b0afb0b4f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.awaitLoaded import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -93,8 +92,8 @@ class RustMatrixClient constructor( private val innerRoomListService = syncService.roomListService() private val sessionDispatcher = dispatchers.io.limitedParallelism(64) private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}") - private val verificationService = RustSessionVerificationService() private val rustSyncService = RustSyncService(syncService, sessionCoroutineScope) + private val verificationService = RustSessionVerificationService(rustSyncService) private val pushersService = RustPushersService( client = client, dispatchers = dispatchers, @@ -149,13 +148,11 @@ class RustMatrixClient constructor( init { client.setDelegate(clientDelegate) - rustSyncService.syncState - .onEach { syncState -> - if (syncState == SyncState.Running) { - onSlidingSyncUpdate() - } + roomListService.state.onEach { state -> + if (state == RoomListService.State.Running) { + setupVerificationControllerIfNeeded() } - .launchIn(sessionCoroutineScope) + }.launchIn(sessionCoroutineScope) } override suspend fun getRoom(roomId: RoomId): MatrixRoom? = withContext(sessionDispatcher) { @@ -338,8 +335,8 @@ class RustMatrixClient constructor( } } - private fun onSlidingSyncUpdate() { - if (!verificationService.isReady.value) { + private fun setupVerificationControllerIfNeeded() { + if (verificationService.verificationController == null) { try { verificationService.verificationController = client.getSessionVerificationController() } catch (e: Throwable) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index dc65bb74a6..29797ed3c4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -17,20 +17,25 @@ package io.element.android.libraries.matrix.impl.verification import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState +import io.element.android.libraries.matrix.impl.sync.RustSyncService import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import org.matrix.rustcomponents.sdk.SessionVerificationController import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate import org.matrix.rustcomponents.sdk.SessionVerificationControllerInterface import org.matrix.rustcomponents.sdk.SessionVerificationEmoji import javax.inject.Inject -class RustSessionVerificationService @Inject constructor() : SessionVerificationService, SessionVerificationControllerDelegate { +class RustSessionVerificationService @Inject constructor( + private val syncService: RustSyncService, +) : SessionVerificationService, SessionVerificationControllerDelegate { var verificationController: SessionVerificationControllerInterface? = null set(value) { @@ -52,6 +57,10 @@ class RustSessionVerificationService @Inject constructor() : SessionVerification private val _sessionVerifiedStatus = MutableStateFlow(SessionVerifiedStatus.Unknown) override val sessionVerifiedStatus: StateFlow = _sessionVerifiedStatus.asStateFlow() + override val canVerifySessionFlow = combine(sessionVerifiedStatus, syncService.syncState) { verificationStatus, syncState -> + syncState == SyncState.Running && verificationStatus == SessionVerifiedStatus.NotVerified + } + override suspend fun requestVerification() = tryOrFail { verificationController?.requestVerification() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index 2f7887c537..62b0b39adf 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -27,13 +28,13 @@ class FakeSessionVerificationService : SessionVerificationService { private val _isReady = MutableStateFlow(false) private val _sessionVerifiedStatus = MutableStateFlow(SessionVerifiedStatus.Unknown) private var _verificationFlowState = MutableStateFlow(VerificationFlowState.Initial) + private var _canVerifySessionFlow = MutableStateFlow(true) private var emojiList = emptyList() var shouldFail = false - override val verificationFlowState: StateFlow - get() = _verificationFlowState - + override val verificationFlowState: StateFlow =_verificationFlowState override val sessionVerifiedStatus: StateFlow = _sessionVerifiedStatus + override val canVerifySessionFlow: Flow = _canVerifySessionFlow override val isReady: StateFlow = _isReady @@ -77,6 +78,10 @@ class FakeSessionVerificationService : SessionVerificationService { _verificationFlowState.value = state } + fun givenCanVerifySession(canVerify: Boolean) { + _canVerifySessionFlow.value = canVerify + } + fun givenIsReady(value: Boolean) { _isReady.value = value } From b6518c595d78ad6d7638b8b75ab0acdc9575b176 Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Thu, 24 Aug 2023 14:14:59 +0200 Subject: [PATCH 26/26] Update rust sdk to 0.1.47 (#1142) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 43c2a25bdb..434c014932 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -146,7 +146,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.46" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.47" sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" }