From d7b22047c63a1df3144a2e6206a9bb3051574c5c 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 549218eeb22160685f0c2399d405d47f926e1a1c 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 16c4801dd3c51f1db94324a4408f5b89ea4068b2 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 1922c505ef85b75e63861227634f8d57742da48c 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 7ea9512642b23a1a724b2475313c6d4f42bc796e 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 e4d4865c4ef33dc05669bb9afdfaff4b6c02d03e 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 7df985baedafdbfcc7d0c3f4fe99bd2f8b2dce0e 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 2910911da22a9e73ee8ac4943a81327fb995a8eb 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 6928dc6e44b939d7a32b168175e7e93fe8278296 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 bc57a03a3939500bdc476ef9817130068a1ca675 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 8cf48986e785dc37030c9ccbe76af0cfc7396ea9 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 d293cae47f176cd00d3f2e2b4738bc8a3cd6de84 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 7eb1f1c7bfef5612e773b66a65612bab0e4f30c1 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 eff3a53c520e9c7a75493021c74a1e4a57f53c3b 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 01bb850ee9a4eb8c285cac98de10075188f2549e 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 b1a1646c4ea689879e6065354268bb7ae23318f8 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 7764b7b36796242f12dd9c9d764a96558ac66086 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 53840c1009a5bac363a41c0a9e21178e9360a61d 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 648e2c378d4bc302c14f03f3e300f130aed7bbd3 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 13ff443b4a3db771ace03365ee80610c372fee0a 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 dd515423ccabf3425a6ae1bb4d776ee07fcec32e 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 f2b7252a8848005d176f4b013e41f7bd6d0f123e 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 82c3b4896c24d243f667cad84d5af17dd2934bf4 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 9fb02162723f38e926a07a843adc628fd0d9845b 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 c670fc9e9c07fcc69b9564e2f1254a5ceb63aa97 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 cbeab3111d15fb7a91fbb24ea01a73eda1ba8e0f 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" }