diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index fb21367501b..2d791f0f9a1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -36,6 +36,7 @@ import io.element.android.features.login.impl.screens.createaccount.CreateAccoun import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode import io.element.android.features.login.impl.screens.onboarding.OnBoardingNode import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs @@ -45,7 +46,6 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.oidc.api.OidcAction import io.element.android.libraries.oidc.api.OidcActionFlow -import io.element.android.libraries.oidc.api.OidcEntryPoint import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -57,7 +57,6 @@ class LoginFlowNode @AssistedInject constructor( private val accountProviderDataSource: AccountProviderDataSource, private val defaultLoginUserStory: DefaultLoginUserStory, private val oidcActionFlow: OidcActionFlow, - private val oidcEntryPoint: OidcEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.OnBoarding, @@ -74,15 +73,15 @@ class LoginFlowNode @AssistedInject constructor( private var activity: Activity? = null private var darkTheme: Boolean = false - private var customChromeTabStarted = false + private var externalAppStarted = false override fun onBuilt() { super.onBuilt() defaultLoginUserStory.setLoginFlowIsDone(false) lifecycle.subscribe( onResume = { - if (customChromeTabStarted) { - customChromeTabStarted = false + if (externalAppStarted) { + externalAppStarted = false // Workaround to detect that the Custom Chrome Tab has been closed // If there is no coming OidcAction (that would end this Node), // consider that the user has cancelled the login @@ -122,9 +121,6 @@ class LoginFlowNode @AssistedInject constructor( @Parcelize data class CreateAccount(val url: String) : NavTarget - - @Parcelize - data class OidcView(val oidcDetails: OidcDetails) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -249,9 +245,6 @@ class LoginFlowNode @AssistedInject constructor( NavTarget.LoginPassword -> { createNode(buildContext) } - is NavTarget.OidcView -> { - oidcEntryPoint.createFallbackWebViewNode(this, buildContext, navTarget.oidcDetails.url) - } is NavTarget.CreateAccount -> { val inputs = CreateAccountNode.Inputs( url = navTarget.url, @@ -262,15 +255,9 @@ class LoginFlowNode @AssistedInject constructor( } private fun navigateToMas(oidcDetails: OidcDetails) { - if (oidcEntryPoint.canUseCustomTab()) { - // In this case open a Chrome Custom tab - activity?.let { - customChromeTabStarted = true - oidcEntryPoint.openUrlInCustomTab(it, darkTheme, oidcDetails.url) - } - } else { - // Fallback to WebView mode - backstack.push(NavTarget.OidcView(oidcDetails)) + activity?.let { + externalAppStarted = true + it.openUrlInChromeCustomTab(null, darkTheme, oidcDetails.url) } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt index c8e9072c6b2..a69a7591b7c 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt @@ -26,6 +26,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.compound.theme.ElementTheme import io.element.android.features.securebackup.impl.reset.password.ResetIdentityPasswordNode import io.element.android.features.securebackup.impl.reset.root.ResetIdentityRootNode import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab @@ -37,7 +38,6 @@ import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle -import io.element.android.libraries.oidc.api.OidcEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectLatest @@ -51,7 +51,6 @@ class ResetIdentityFlowNode @AssistedInject constructor( @Assisted plugins: List, private val resetIdentityFlowManager: ResetIdentityFlowManager, private val coroutineScope: CoroutineScope, - private val oidcEntryPoint: OidcEntryPoint, ) : BaseFlowNode( backstack = BackStack(initialElement = NavTarget.Root, savedStateMap = buildContext.savedStateMap), buildContext = buildContext, @@ -67,12 +66,10 @@ class ResetIdentityFlowNode @AssistedInject constructor( @Parcelize data object ResetPassword : NavTarget - - @Parcelize - data class ResetOidc(val url: String) : NavTarget } private lateinit var activity: Activity + private var darkTheme: Boolean = false private var resetJob: Job? = null override fun onBuilt() { @@ -80,7 +77,7 @@ class ResetIdentityFlowNode @AssistedInject constructor( lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { - // If the custom tab was opened, we need to cancel the reset job + // If the custom tab / Web browser was opened, we need to cancel the reset job // when we come back to the node if the reset wasn't successful coroutineScope.launch { cancelResetJob() @@ -115,9 +112,6 @@ class ResetIdentityFlowNode @AssistedInject constructor( listOf(ResetIdentityPasswordNode.Inputs(handle)) ) } - is NavTarget.ResetOidc -> { - oidcEntryPoint.createFallbackWebViewNode(this, buildContext, navTarget.url) - } } } @@ -135,11 +129,7 @@ class ResetIdentityFlowNode @AssistedInject constructor( Timber.d("No reset handle return, the reset is done.") } is IdentityOidcResetHandle -> { - if (oidcEntryPoint.canUseCustomTab()) { - activity.openUrlInChromeCustomTab(null, false, handle.url) - } else { - backstack.push(NavTarget.ResetOidc(handle.url)) - } + activity.openUrlInChromeCustomTab(null, darkTheme, handle.url) resetJob = launch { handle.resetOidc() } } is IdentityPasswordResetHandle -> backstack.push(NavTarget.ResetPassword) @@ -162,7 +152,7 @@ class ResetIdentityFlowNode @AssistedInject constructor( if (!this::activity.isInitialized) { activity = requireNotNull(LocalActivity.current) } - + darkTheme = !ElementTheme.isLightTheme val startResetState by resetIdentityFlowManager.currentHandleFlow.collectAsState() if (startResetState.isLoading()) { ProgressDialog( diff --git a/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcEntryPoint.kt b/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcEntryPoint.kt deleted file mode 100644 index d216afc34f4..00000000000 --- a/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcEntryPoint.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.api - -import android.app.Activity -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node - -interface OidcEntryPoint { - fun canUseCustomTab(): Boolean - fun openUrlInCustomTab(activity: Activity, darkTheme: Boolean, url: String) - fun createFallbackWebViewNode(parentNode: Node, buildContext: BuildContext, url: String): Node -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/CustomTabAvailabilityChecker.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/CustomTabAvailabilityChecker.kt deleted file mode 100644 index 077331dbdf5..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/CustomTabAvailabilityChecker.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl - -import android.content.Context -import androidx.browser.customtabs.CustomTabsClient -import io.element.android.libraries.di.ApplicationContext -import javax.inject.Inject - -class CustomTabAvailabilityChecker @Inject constructor( - @ApplicationContext private val context: Context, -) { - /** - * Return true if the device supports Custom tab, i.e. there is an third party app with - * CustomTab support (ex: Chrome, Firefox, etc.). - */ - fun supportCustomTab(): Boolean { - val packageName = CustomTabsClient.getPackageName(context, null) - return packageName != null - } -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/customtab/DefaultOidcActionFlow.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlow.kt similarity index 95% rename from libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/customtab/DefaultOidcActionFlow.kt rename to libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlow.kt index 39b67957255..e975cde261b 100644 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/customtab/DefaultOidcActionFlow.kt +++ b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlow.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.oidc.impl.customtab +package io.element.android.libraries.oidc.impl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcEntryPoint.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcEntryPoint.kt deleted file mode 100644 index 00f8838d42f..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcEntryPoint.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl - -import android.app.Activity -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab -import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.di.AppScope -import io.element.android.libraries.matrix.api.auth.OidcDetails -import io.element.android.libraries.oidc.api.OidcEntryPoint -import io.element.android.libraries.oidc.impl.webview.OidcNode -import javax.inject.Inject - -@ContributesBinding(AppScope::class) -class DefaultOidcEntryPoint @Inject constructor( - private val customTabAvailabilityChecker: CustomTabAvailabilityChecker, -) : OidcEntryPoint { - override fun canUseCustomTab(): Boolean { - return customTabAvailabilityChecker.supportCustomTab() - } - - override fun openUrlInCustomTab(activity: Activity, darkTheme: Boolean, url: String) { - assert(canUseCustomTab()) { "Custom tab is not supported in this device." } - activity.openUrlInChromeCustomTab(null, darkTheme, url) - } - - override fun createFallbackWebViewNode(parentNode: Node, buildContext: BuildContext, url: String): Node { - assert(!canUseCustomTab()) { "Custom tab should be used instead of the fallback node." } - val inputs = OidcNode.Inputs(OidcDetails(url)) - return parentNode.createNode(buildContext, listOf(inputs)) - } -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/customtab/CustomTabHandler.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/customtab/CustomTabHandler.kt deleted file mode 100644 index 64c21596c16..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/customtab/CustomTabHandler.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.customtab - -import android.app.Activity -import android.content.ComponentName -import android.content.Context -import androidx.browser.customtabs.CustomTabsClient -import androidx.browser.customtabs.CustomTabsServiceConnection -import androidx.browser.customtabs.CustomTabsSession -import androidx.core.net.toUri -import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab -import io.element.android.libraries.di.ApplicationContext -import javax.inject.Inject - -class CustomTabHandler @Inject constructor( - @ApplicationContext private val context: Context, -) { - private var customTabsSession: CustomTabsSession? = null - private var customTabsClient: CustomTabsClient? = null - private var customTabsServiceConnection: CustomTabsServiceConnection? = null - - fun prepareCustomTab(url: String) { - val packageName = CustomTabsClient.getPackageName(context, null) - - // packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device - if (packageName != null) { - customTabsServiceConnection = object : CustomTabsServiceConnection() { - override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { - customTabsClient = client.apply { warmup(0L) } - prefetchUrl(url) - } - - override fun onServiceDisconnected(name: ComponentName?) { - } - } - .also { - CustomTabsClient.bindCustomTabsService( - context, - // Despite the API, packageName cannot be null - packageName, - it - ) - } - } - } - - private fun prefetchUrl(url: String) { - if (customTabsSession == null) { - customTabsSession = customTabsClient?.newSession(null) - } - - customTabsSession?.mayLaunchUrl(url.toUri(), null, null) - } - - fun disposeCustomTab() { - customTabsServiceConnection?.let { context.unbindService(it) } - customTabsServiceConnection = null - } - - fun open(activity: Activity, darkTheme: Boolean, url: String) { - activity.openUrlInChromeCustomTab(customTabsSession, darkTheme, url) - } -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcEvents.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcEvents.kt deleted file mode 100644 index 165ef00ebcf..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcEvents.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.webview - -import io.element.android.libraries.oidc.api.OidcAction - -sealed interface OidcEvents { - data object Cancel : OidcEvents - data class OidcActionEvent(val oidcAction: OidcAction) : OidcEvents - data object ClearError : OidcEvents -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcNode.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcNode.kt deleted file mode 100644 index a9141dc333b..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcNode.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.webview - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.architecture.NodeInputs -import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.di.AppScope -import io.element.android.libraries.matrix.api.auth.OidcDetails -import io.element.android.libraries.oidc.impl.OidcUrlParser - -@ContributesNode(AppScope::class) -class OidcNode @AssistedInject constructor( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, - presenterFactory: OidcPresenter.Factory, - private val oidcUrlParser: OidcUrlParser, -) : Node(buildContext, plugins = plugins) { - data class Inputs( - val oidcDetails: OidcDetails, - ) : NodeInputs - - private val inputs: Inputs = inputs() - private val presenter = presenterFactory.create(inputs.oidcDetails) - - @Composable - override fun View(modifier: Modifier) { - val state = presenter.present() - OidcView( - state = state, - oidcUrlParser = oidcUrlParser, - modifier = modifier, - onNavigateBack = ::navigateUp, - ) - } -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenter.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenter.kt deleted file mode 100644 index f8144d69a6b..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenter.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.webview - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService -import io.element.android.libraries.matrix.api.auth.OidcDetails -import io.element.android.libraries.oidc.api.OidcAction -import kotlinx.coroutines.launch - -class OidcPresenter @AssistedInject constructor( - @Assisted private val oidcDetails: OidcDetails, - private val authenticationService: MatrixAuthenticationService, -) : Presenter { - @AssistedFactory - interface Factory { - fun create(oidcDetails: OidcDetails): OidcPresenter - } - - @Composable - override fun present(): OidcState { - var requestState: AsyncAction by remember { - mutableStateOf(AsyncAction.Uninitialized) - } - val localCoroutineScope = rememberCoroutineScope() - - fun handleCancel() { - requestState = AsyncAction.Loading - localCoroutineScope.launch { - authenticationService.cancelOidcLogin() - .fold( - onSuccess = { - // Then go back - requestState = AsyncAction.Success(Unit) - }, - onFailure = { - requestState = AsyncAction.Failure(it) - } - ) - } - } - - fun handleSuccess(url: String) { - requestState = AsyncAction.Loading - localCoroutineScope.launch { - authenticationService.loginWithOidc(url) - .onFailure { - requestState = AsyncAction.Failure(it) - } - // On success, the node tree will be updated, there is nothing to do - } - } - - fun handleAction(action: OidcAction) { - when (action) { - OidcAction.GoBack -> handleCancel() - is OidcAction.Success -> handleSuccess(action.url) - } - } - - fun handleEvents(event: OidcEvents) { - when (event) { - OidcEvents.Cancel -> handleCancel() - is OidcEvents.OidcActionEvent -> handleAction(event.oidcAction) - OidcEvents.ClearError -> requestState = AsyncAction.Uninitialized - } - } - - return OidcState( - oidcDetails = oidcDetails, - requestState = requestState, - eventSink = ::handleEvents - ) - } -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcState.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcState.kt deleted file mode 100644 index 4358d70179a..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcState.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.webview - -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.auth.OidcDetails - -data class OidcState( - val oidcDetails: OidcDetails, - val requestState: AsyncAction, - val eventSink: (OidcEvents) -> Unit -) diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcStateProvider.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcStateProvider.kt deleted file mode 100644 index 52d90a86a57..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcStateProvider.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.webview - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.auth.OidcDetails - -open class OidcStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aOidcState(), - aOidcState().copy(requestState = AsyncAction.Loading), - ) -} - -fun aOidcState() = OidcState( - oidcDetails = aOidcDetails(), - requestState = AsyncAction.Uninitialized, - eventSink = {} -) - -fun aOidcDetails() = OidcDetails( - url = "aUrl", -) diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcView.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcView.kt deleted file mode 100644 index 13b80dd6002..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcView.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.webview - -import android.annotation.SuppressLint -import android.webkit.WebView -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.viewinterop.AndroidView -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.designsystem.components.async.AsyncActionView -import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.components.Scaffold -import io.element.android.libraries.designsystem.theme.components.TopAppBar -import io.element.android.libraries.oidc.impl.OidcUrlParser - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun OidcView( - state: OidcState, - oidcUrlParser: OidcUrlParser, - onNavigateBack: () -> Unit, - modifier: Modifier = Modifier, -) { - val isPreview = LocalInspectionMode.current - var webView by remember { mutableStateOf(null) } - fun shouldOverrideUrl(url: String): Boolean { - val action = oidcUrlParser.parse(url) - if (action != null) { - state.eventSink.invoke(OidcEvents.OidcActionEvent(action)) - return true - } - return false - } - - val oidcWebViewClient = remember { - OidcWebViewClient(::shouldOverrideUrl) - } - - fun onBack() { - if (webView?.canGoBack().orFalse()) { - webView?.goBack() - } else { - // To properly cancel Oidc login - state.eventSink.invoke(OidcEvents.Cancel) - } - } - - BackHandler { onBack() } - - Scaffold( - modifier = modifier, - topBar = { - TopAppBar( - title = {}, - navigationIcon = { - BackButton(onClick = ::onBack) - }, - ) - } - ) { contentPadding -> - AndroidView( - modifier = Modifier.padding(contentPadding), - factory = { context -> - WebView(context).apply { - if (!isPreview) { - webViewClient = oidcWebViewClient - settings.apply { - @SuppressLint("SetJavaScriptEnabled") - javaScriptEnabled = true - allowContentAccess = true - allowFileAccess = true - @Suppress("DEPRECATION") - databaseEnabled = true - domStorageEnabled = true - } - loadUrl(state.oidcDetails.url) - } - }.also { - webView = it - } - } - ) - - AsyncActionView( - async = state.requestState, - onSuccess = { onNavigateBack() }, - onErrorDismiss = { state.eventSink(OidcEvents.ClearError) } - ) - } -} - -@PreviewsDayNight -@Composable -internal fun OidcViewPreview(@PreviewParameter(OidcStateProvider::class) state: OidcState) = ElementPreview { - OidcView( - state = state, - oidcUrlParser = { null }, - onNavigateBack = {}, - ) -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcWebViewClient.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcWebViewClient.kt deleted file mode 100644 index 0d00c790fb5..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/OidcWebViewClient.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.webview - -import android.webkit.WebResourceRequest -import android.webkit.WebView -import android.webkit.WebViewClient - -class OidcWebViewClient( - private val eventListener: WebViewEventListener, -) : WebViewClient() { - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { - return shouldOverrideUrl(request.url.toString()) - } - - @Deprecated("Deprecated in Java") - override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { - return shouldOverrideUrl(url) - } - - private fun shouldOverrideUrl(url: String): Boolean { - return eventListener.shouldOverrideUrlLoading(url) - } -} diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/WebViewEventListener.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/WebViewEventListener.kt deleted file mode 100644 index def6e54c716..00000000000 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/webview/WebViewEventListener.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.oidc.impl.webview - -fun interface WebViewEventListener { - /** - * Triggered when a Webview loads an url. - * - * @param url The url about to be rendered. - * @return true if the method needs to manage some custom handling - */ - fun shouldOverrideUrlLoading(url: String): Boolean -} diff --git a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/customtab/DefaultOidcActionFlowTest.kt b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlowTest.kt similarity index 94% rename from libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/customtab/DefaultOidcActionFlowTest.kt rename to libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlowTest.kt index 104ff6c3471..3b56f28c5a6 100644 --- a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/customtab/DefaultOidcActionFlowTest.kt +++ b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlowTest.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.oidc.impl.customtab +package io.element.android.libraries.oidc.impl import com.google.common.truth.Truth.assertThat import io.element.android.libraries.oidc.api.OidcAction diff --git a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenterTest.kt b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenterTest.kt deleted file mode 100644 index 118951dac5f..00000000000 --- a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenterTest.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package io.element.android.libraries.oidc.impl.webview - -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.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.test.AN_EXCEPTION -import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA -import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService -import io.element.android.libraries.oidc.api.OidcAction -import io.element.android.tests.testutils.WarmUpRule -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import org.junit.Rule -import org.junit.Test - -class OidcPresenterTest { - @get:Rule - val warmUpRule = WarmUpRule() - - @Test - fun `present - initial state`() = runTest { - val presenter = OidcPresenter( - A_OIDC_DATA, - FakeMatrixAuthenticationService(), - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.oidcDetails).isEqualTo(A_OIDC_DATA) - assertThat(initialState.requestState).isEqualTo(AsyncAction.Uninitialized) - } - } - - @Test - fun `present - go back`() = runTest { - val presenter = OidcPresenter( - A_OIDC_DATA, - FakeMatrixAuthenticationService(), - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(OidcEvents.Cancel) - val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) - val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(AsyncAction.Success(Unit)) - } - } - - @Test - fun `present - go back with failure`() = runTest { - val authenticationService = FakeMatrixAuthenticationService() - val presenter = OidcPresenter( - A_OIDC_DATA, - authenticationService, - ) - authenticationService.givenOidcCancelError(AN_EXCEPTION) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(OidcEvents.Cancel) - val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) - val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - // Note: in real life I do not think this can happen, and the app should not block the user. - } - } - - @Test - fun `present - user cancels from webview`() = runTest { - val presenter = OidcPresenter( - A_OIDC_DATA, - FakeMatrixAuthenticationService(), - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.GoBack)) - val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) - val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(AsyncAction.Success(Unit)) - } - } - - @Test - fun `present - login success`() = runTest { - val presenter = OidcPresenter( - A_OIDC_DATA, - FakeMatrixAuthenticationService(), - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL"))) - val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) - // In this case, no success, the session is created and the node get destroyed. - } - } - - @Test - fun `present - login error`() = runTest { - val authenticationService = FakeMatrixAuthenticationService() - val presenter = OidcPresenter( - A_OIDC_DATA, - authenticationService, - ) - authenticationService.givenLoginError(AN_EXCEPTION) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL"))) - val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) - val errorState = awaitItem() - assertThat(errorState.requestState).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - errorState.eventSink.invoke(OidcEvents.ClearError) - val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(AsyncAction.Uninitialized) - } - } -} diff --git a/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Day_0_en.png deleted file mode 100644 index 785f56af7f5..00000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:154a56d848cc735efc25274052ce92b1a20bc8f80f75e91d0d4cfc7d488cd246 -size 5874 diff --git a/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Day_1_en.png deleted file mode 100644 index 3f13dad18f5..00000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ff42261f4d991650578ceadaa13be0b1924c1af8f71cf10832f53c7d958827d -size 9317 diff --git a/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Night_0_en.png deleted file mode 100644 index c41c1c79218..00000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2dd3462935091cfe022f0e6fc71b082eed7dab4769def13398a81a90f871b61a -size 5807 diff --git a/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Night_1_en.png deleted file mode 100644 index 17ecfab0903..00000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.oidc.impl.webview_OidcView_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e6158b2d0447199de84156fbfdc7bd9aa1af7ff2092b498db254ded465605b76 -size 8208