Repository: element-hq/element-x-android (language: kotlin)

Issue:
[Task] Deeplink to specify account provider & user
Please see and discuss the details in the meta issue.

Multiple "instances" open when sharing media
### Steps to reproduce

First I mainly use SchildiChat Next, but I also tested it with element x, and the issue there still persists.

When sharing media via other apps (like YouTube or something), a new "instance" or window, whatever you want to call it, is created, and clog up the view of open apps

**To Reproduce**
Steps to reproduce the behavior:
1. Go to your app to you liking and share media, to any room
2. Element won't close the window, in which the media was shared.
3. If you repeat this a few times, there will e many windows of element open

### Outcome

**Expected behavior**
Either close the window after sharing, or open the sharing process in the already existing windows of element

**Screenshots**
![IMG_20241220_002347](https://github.com/user-attachments/assets/18a402e2-74ef-4b0f-91b8-3aa9276172aa)

### Your phone model

Oneus 9 Pro

### Operating system version

Android 14

### Application version and app store

SchildiChat Next 0.7.5

### Homeserver

Synapse Docker v1.121.1

### Will you send logs?

Yes

### Are you willing to provide a PR?

No


Discussion hints:

Pull request: Add support for login link
<!-- Please read [CONTRIBUTING.md](https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
 
## Content

<!-- Describe shortly what has been changed -->
Add support for login link

## Motivation and context

<!-- Provide link to the corresponding issue if applicable or explain the context -->
Let the application be opened when the user opens link like

https://mobile.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org

Closes #4705

## Screenshots / GIFs

<!--
We have screenshot tests in the project, so attaching screenshots to a PR is not mandatory, as far as there
is a Composable Preview covering the changes. In this case, the change will appear in the file diff.
Note that all the UI composables should be covered by a Composable Preview.

Providing a video of the change is still very useful for the reviewer and for the history of the project.

You can use a table like this to show screenshots comparison.
Uncomment this markdown table below and edit the last line `|||`:
|copy screenshot of before here|copy screenshot of after here|

|Before|After|
|-|-|
|||
 -->

https://github.com/user-attachments/assets/4a25f676-47e9-41f0-ad6c-ffa88d4db3be


## Tests

<!-- Explain how you tested your development -->

- Install the app
- Open a link such as https://mobile.element.io/element?account_provider=element.io&login_hint=mxid:@alice:element.io
- The system should start the app with a button to sign in to element.io
- kill and start the app again
- The app render the classical onboarding screen

## Tested devices

- [ ] Physical
- [x] Emulator
- OS version(s):

## Checklist

<!-- Depending on the Pull Request content, it can be acceptable if some of the following checkboxes stay unchecked. -->

- [ ] Changes have been tested on an Android device or Android emulator with API 24
- [ ] UI change has been tested on both light and dark themes
- [ ] Accessibility has been taken into account. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#accessibility
- [ ] Pull request is based on the develop branch
- [ ] Pull request title will be used in the release note, it clearly define what will change for the user
- [ ] Pull request includes screenshots or videos if containing UI changes
- [ ] You've made a self review of your PR


Test command: ./gradlew :features:login:impl:testDebugUnitTest --tests "io.element.android.features.login.impl.screens.onboarding.OnBoardingPresenterTest" --tests "io.element.android.features.login.impl.DefaultLoginIntentResolverTest" --tests "io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderPresenterTest" --tests "io.element.android.features.login.impl.screens.onboarding.OnboardingViewTest" --tests "io.element.android.features.login.impl.onboarding.OnBoardingPresenterTest" :appnav:testDebugUnitTest --tests "io.element.android.appnav.intent.IntentResolverTest"

Fix patch:
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c26c9a5a513..6619603f937 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,11 +34,17 @@
                 android:value='androidx.startup' />
         </provider>
 
+        <!--
+        Using launchMode singleTask to avoid multiple instances of the Activity
+        when the app is already open. This is important for incoming share (see
+        https://github.com/element-hq/element-x-android/issues/4074) and for opening
+        the application from a mobile.element.io link.
+        -->
         <activity
             android:name=".MainActivity"
             android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode"
             android:exported="true"
-            android:launchMode="singleTop"
+            android:launchMode="singleTask"
             android:theme="@style/Theme.ElementX.Splash"
             android:windowSoftInputMode="adjustResize">
             <intent-filter>
@@ -54,6 +60,9 @@
                     android:host="open"
                     android:scheme="elementx" />
             </intent-filter>
+            <!--
+              Oidc redirection
+            -->
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
 
@@ -80,6 +89,21 @@
                 <!-- Matching asset file: https://staging.element.io/.well-known/assetlinks.json -->
                 <data android:host="staging.element.io" />
             </intent-filter>
+            <!--
+              Element mobile links
+              Example: https://mobile.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org
+            -->
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="https" />
+                <!-- Matching asset file: https://mobile.element.io/.well-known/assetlinks.json -->
+                <data android:host="mobile.element.io" />
+                <data android:path="/element" />
+            </intent-filter>
             <!--
               matrix.to links
               Note: On Android 12 and higher clicking a web link (that is not an Android App Link) always shows content in a web browser
diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts
index 8b2257b4a3b..a5fe7f0ec39 100644
--- a/appnav/build.gradle.kts
+++ b/appnav/build.gradle.kts
@@ -53,6 +53,7 @@ dependencies {
     testImplementation(libs.molecule.runtime)
     testImplementation(libs.test.truth)
     testImplementation(libs.test.turbine)
+    testImplementation(projects.features.login.test)
     testImplementation(projects.libraries.matrix.test)
     testImplementation(projects.libraries.oidc.impl)
     testImplementation(projects.libraries.preferences.test)
diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt
index bcd1c5a61e8..bd4802b77ba 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt
@@ -24,8 +24,11 @@ import dagger.assisted.Assisted
 import dagger.assisted.AssistedInject
 import io.element.android.anvilannotations.ContributesNode
 import io.element.android.features.login.api.LoginEntryPoint
+import io.element.android.features.login.api.LoginParams
 import io.element.android.libraries.architecture.BackstackView
 import io.element.android.libraries.architecture.BaseFlowNode
+import io.element.android.libraries.architecture.NodeInputs
+import io.element.android.libraries.architecture.inputs
 import io.element.android.libraries.designsystem.utils.ForceOrientationInMobileDevices
 import io.element.android.libraries.designsystem.utils.ScreenOrientation
 import io.element.android.libraries.di.AppScope
@@ -46,10 +49,16 @@ class NotLoggedInFlowNode @AssistedInject constructor(
     buildContext = buildContext,
     plugins = plugins,
 ) {
+    data class Params(
+        val loginParams: LoginParams?,
+    ) : NodeInputs
+
     interface Callback : Plugin {
         fun onOpenBugReport()
     }
 
+    private val inputs = inputs<Params>()
+
     override fun onBuilt() {
         super.onBuilt()
         lifecycle.subscribe(
@@ -74,6 +83,12 @@ class NotLoggedInFlowNode @AssistedInject constructor(
                 }
                 loginEntryPoint
                     .nodeBuilder(this, buildContext)
+                    .params(
+                        LoginEntryPoint.Params(
+                            accountProvider = inputs.loginParams?.accountProvider,
+                            loginHint = inputs.loginParams?.loginHint,
+                        )
+                    )
                     .callback(callback)
                     .build()
             }
diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
index c52ee9cb5d0..ae9f935061c 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
@@ -33,6 +33,8 @@ import io.element.android.appnav.intent.ResolvedIntent
 import io.element.android.appnav.root.RootNavStateFlowFactory
 import io.element.android.appnav.root.RootPresenter
 import io.element.android.appnav.root.RootView
+import io.element.android.features.enterprise.api.EnterpriseService
+import io.element.android.features.login.api.LoginParams
 import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
 import io.element.android.features.signedout.api.SignedOutEntryPoint
 import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
@@ -40,6 +42,7 @@ import io.element.android.libraries.architecture.BackstackView
 import io.element.android.libraries.architecture.BaseFlowNode
 import io.element.android.libraries.architecture.createNode
 import io.element.android.libraries.architecture.waitForChildAttached
+import io.element.android.libraries.core.uri.ensureProtocol
 import io.element.android.libraries.deeplink.DeeplinkData
 import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
 import io.element.android.libraries.di.AppScope
@@ -61,6 +64,7 @@ class RootFlowNode @AssistedInject constructor(
     @Assisted val buildContext: BuildContext,
     @Assisted plugins: List<Plugin>,
     private val authenticationService: MatrixAuthenticationService,
+    private val enterpriseService: EnterpriseService,
     private val navStateFlowFactory: RootNavStateFlowFactory,
     private val matrixSessionCache: MatrixSessionCache,
     private val presenter: RootPresenter,
@@ -99,14 +103,14 @@ class RootFlowNode @AssistedInject constructor(
                         if (navState.loggedInState.isTokenValid) {
                             tryToRestoreLatestSession(
                                 onSuccess = { sessionId -> switchToLoggedInFlow(sessionId, navState.cacheIndex) },
-                                onFailure = { switchToNotLoggedInFlow() }
+                                onFailure = { switchToNotLoggedInFlow(null) }
                             )
                         } else {
                             switchToSignedOutFlow(SessionId(navState.loggedInState.sessionId))
                         }
                     }
                     LoggedInState.NotLoggedIn -> {
-                        switchToNotLoggedInFlow()
+                        switchToNotLoggedInFlow(null)
                     }
                 }
             }
@@ -117,9 +121,9 @@ class RootFlowNode @AssistedInject constructor(
         backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, navId))
     }
 
-    private fun switchToNotLoggedInFlow() {
+    private fun switchToNotLoggedInFlow(params: LoginParams?) {
         matrixSessionCache.removeAll()
-        backstack.safeRoot(NavTarget.NotLoggedInFlow)
+        backstack.safeRoot(NavTarget.NotLoggedInFlow(params))
     }
 
     private fun switchToSignedOutFlow(sessionId: SessionId) {
@@ -175,7 +179,9 @@ class RootFlowNode @AssistedInject constructor(
         data object SplashScreen : NavTarget
 
         @Parcelize
-        data object NotLoggedInFlow : NavTarget
+        data class NotLoggedInFlow(
+            val params: LoginParams?
+        ) : NavTarget
 
         @Parcelize
         data class LoggedInFlow(
@@ -211,13 +217,16 @@ class RootFlowNode @AssistedInject constructor(
                 }
                 createNode<LoggedInAppScopeFlowNode>(buildContext, plugins = listOf(inputs, callback))
             }
-            NavTarget.NotLoggedInFlow -> {
+            is NavTarget.NotLoggedInFlow -> {
                 val callback = object : NotLoggedInFlowNode.Callback {
                     override fun onOpenBugReport() {
                         backstack.push(NavTarget.BugReport)
                     }
                 }
-                createNode<NotLoggedInFlowNode>(buildContext, plugins = listOf(callback))
+                val params = NotLoggedInFlowNode.Params(
+                    loginParams = navTarget.params,
+                )
+                createNode<NotLoggedInFlowNode>(buildContext, plugins = listOf(params, callback))
             }
             is NavTarget.SignedOutFlow -> {
                 signedOutEntryPoint.nodeBuilder(this, buildContext)
@@ -272,18 +281,36 @@ class RootFlowNode @AssistedInject constructor(
         val resolvedIntent = intentResolver.resolve(intent) ?: return
         when (resolvedIntent) {
             is ResolvedIntent.Navigation -> navigateTo(resolvedIntent.deeplinkData)
+            is ResolvedIntent.Login -> onLoginLink(resolvedIntent.params)
             is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction)
             is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData)
             is ResolvedIntent.IncomingShare -> onIncomingShare(resolvedIntent.intent)
         }
     }
 
+    private suspend fun onLoginLink(params: LoginParams) {
+        // Is there a session already?
+        val latestSessionId = authenticationService.getLatestSessionId()
+        if (latestSessionId == null) {
+            // No session, open login
+            if (enterpriseService.isAllowedToConnectToHomeserver(params.accountProvider.ensureProtocol())) {
+                switchToNotLoggedInFlow(params)
+            } else {
+                Timber.w("Login link ignored, we are not allowed to connect to the homeserver")
+                switchToNotLoggedInFlow(null)
+            }
+        } else {
+            // Just ignore the login link if we already have a session
+            Timber.w("Login link ignored, we already have a session")
+        }
+    }
+
     private suspend fun onIncomingShare(intent: Intent) {
         // Is there a session already?
         val latestSessionId = authenticationService.getLatestSessionId()
         if (latestSessionId == null) {
             // No session, open login
-            switchToNotLoggedInFlow()
+            switchToNotLoggedInFlow(null)
         } else {
             attachSession(latestSessionId)
                 .attachIncomingShare(intent)
diff --git a/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt b/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt
index 8e802c323ee..786b694c3fd 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt
@@ -8,6 +8,8 @@
 package io.element.android.appnav.intent
 
 import android.content.Intent
+import io.element.android.features.login.api.LoginIntentResolver
+import io.element.android.features.login.api.LoginParams
 import io.element.android.libraries.deeplink.DeeplinkData
 import io.element.android.libraries.deeplink.DeeplinkParser
 import io.element.android.libraries.matrix.api.permalink.PermalinkData
@@ -21,11 +23,13 @@ sealed interface ResolvedIntent {
     data class Navigation(val deeplinkData: DeeplinkData) : ResolvedIntent
     data class Oidc(val oidcAction: OidcAction) : ResolvedIntent
     data class Permalink(val permalinkData: PermalinkData) : ResolvedIntent
+    data class Login(val params: LoginParams) : ResolvedIntent
     data class IncomingShare(val intent: Intent) : ResolvedIntent
 }
 
 class IntentResolver @Inject constructor(
     private val deeplinkParser: DeeplinkParser,
+    private val loginIntentResolver: LoginIntentResolver,
     private val oidcIntentResolver: OidcIntentResolver,
     private val permalinkParser: PermalinkParser,
 ) {
@@ -40,10 +44,17 @@ class IntentResolver @Inject constructor(
         val oidcAction = oidcIntentResolver.resolve(intent)
         if (oidcAction != null) return ResolvedIntent.Oidc(oidcAction)
 
-        // External link clicked? (matrix.to, element.io, etc.)
-        val permalinkData = intent
+        val actionViewData = intent
             .takeIf { it.action == Intent.ACTION_VIEW }
             ?.dataString
+
+        // Mobile configuration link clicked? (mobile.element.io)
+        val mobileLoginData = actionViewData
+            ?.let { loginIntentResolver.parse(it) }
+        if (mobileLoginData != null) return ResolvedIntent.Login(mobileLoginData)
+
+        // External link clicked? (matrix.to, element.io, etc.)
+        val permalinkData = actionViewData
             ?.let { permalinkParser.parse(it) }
             ?.takeIf { it !is PermalinkData.FallbackLink }
         if (permalinkData != null) return ResolvedIntent.Permalink(permalinkData)
diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt
index 9914eb1eee7..b3ef99ee198 100644
--- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt
+++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt
@@ -13,6 +13,11 @@ import com.bumble.appyx.core.plugin.Plugin
 import io.element.android.libraries.architecture.FeatureEntryPoint
 
 interface LoginEntryPoint : FeatureEntryPoint {
+    data class Params(
+        val accountProvider: String?,
+        val loginHint: String?,
+    )
+
     interface Callback : Plugin {
         fun onReportProblem()
     }
@@ -20,6 +25,7 @@ interface LoginEntryPoint : FeatureEntryPoint {
     fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
 
     interface NodeBuilder {
+        fun params(params: Params): NodeBuilder
         fun callback(callback: Callback): NodeBuilder
         fun build(): Node
     }
diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginIntentResolver.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginIntentResolver.kt
new file mode 100644
index 00000000000..67eb284944d
--- /dev/null
+++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginIntentResolver.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.api
+
+interface LoginIntentResolver {
+    fun parse(uriString: String): LoginParams?
+}
diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginParams.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginParams.kt
new file mode 100644
index 00000000000..84dcac40a65
--- /dev/null
+++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginParams.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.api
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+/**
+ * Parameters to start the login flow, when the application is opened
+ * from a mobile.element.io link.
+ */
+@Parcelize
+data class LoginParams(
+    val accountProvider: String,
+    val loginHint: String?
+) : Parcelable
diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts
index 31d31419202..9fb87aa7f18 100644
--- a/features/login/impl/build.gradle.kts
+++ b/features/login/impl/build.gradle.kts
@@ -58,6 +58,7 @@ dependencies {
     testImplementation(libs.test.robolectric)
     testImplementation(libs.test.truth)
     testImplementation(libs.test.turbine)
+    testImplementation(projects.features.login.test)
     testImplementation(projects.features.enterprise.test)
     testImplementation(projects.libraries.featureflag.test)
     testImplementation(projects.libraries.matrix.test)
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt
index cec04c9cb79..b6eca77a497 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt
@@ -22,6 +22,14 @@ class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint {
         val plugins = ArrayList<Plugin>()
 
         return object : LoginEntryPoint.NodeBuilder {
+            override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder {
+                plugins += LoginFlowNode.Params(
+                    accountProvider = params.accountProvider,
+                    loginHint = params.loginHint,
+                )
+                return this
+            }
+
             override fun callback(callback: LoginEntryPoint.Callback): LoginEntryPoint.NodeBuilder {
                 plugins += callback
                 return this
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolver.kt
new file mode 100644
index 00000000000..ab7117e5e21
--- /dev/null
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolver.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.impl
+
+import androidx.core.net.toUri
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.features.login.api.LoginIntentResolver
+import io.element.android.features.login.api.LoginParams
+import io.element.android.libraries.di.AppScope
+import javax.inject.Inject
+
+@ContributesBinding(AppScope::class)
+class DefaultLoginIntentResolver @Inject constructor() : LoginIntentResolver {
+    override fun parse(uriString: String): LoginParams? {
+        val uri = uriString.toUri()
+        if (uri.host != "mobile.element.io") return null
+        if (uri.path?.startsWith("/element")?.not() == true) return null
+        val accountProvider = uri.getQueryParameter("account_provider") ?: return null
+        val loginHint = uri.getQueryParameter("login_hint")
+        return LoginParams(
+            accountProvider = accountProvider,
+            loginHint = loginHint,
+        )
+    }
+}
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 382180c8565..9efce719d84 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
@@ -28,16 +28,18 @@ import io.element.android.anvilannotations.ContributesNode
 import io.element.android.compound.theme.ElementTheme
 import io.element.android.features.login.api.LoginEntryPoint
 import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
-import io.element.android.features.login.impl.onboarding.OnBoardingNode
 import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
 import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
 import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
 import io.element.android.features.login.impl.screens.createaccount.CreateAccountNode
 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.architecture.BackstackView
 import io.element.android.libraries.architecture.BaseFlowNode
+import io.element.android.libraries.architecture.NodeInputs
 import io.element.android.libraries.architecture.createNode
+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.api.OidcAction
@@ -63,6 +65,11 @@ class LoginFlowNode @AssistedInject constructor(
     buildContext = buildContext,
     plugins = plugins,
 ) {
+    data class Params(
+        val accountProvider: String?,
+        val loginHint: String?,
+    ) : NodeInputs
+
     private var activity: Activity? = null
     private var darkTheme: Boolean = false
 
@@ -139,8 +146,25 @@ class LoginFlowNode @AssistedInject constructor(
                     override fun onReportProblem() {
                         plugins<LoginEntryPoint.Callback>().forEach { it.onReportProblem() }
                     }
+
+                    override fun onOidcDetails(oidcDetails: OidcDetails) {
+                        navigateToMas(oidcDetails)
+                    }
+
+                    override fun onCreateAccountContinue(url: String) {
+                        backstack.push(NavTarget.CreateAccount(url))
+                    }
+
+                    override fun onLoginPasswordNeeded() {
+                        backstack.push(NavTarget.LoginPassword)
+                    }
                 }
-                createNode<OnBoardingNode>(buildContext, listOf(callback))
+                val params = inputs<Params>()
+                val inputs = OnBoardingNode.Params(
+                    accountProvider = params.accountProvider,
+                    loginHint = params.loginHint,
+                )
+                createNode<OnBoardingNode>(buildContext, listOf(callback, inputs))
             }
             NavTarget.QrCode -> {
                 createNode<QrCodeLoginFlowNode>(buildContext)
@@ -151,16 +175,7 @@ class LoginFlowNode @AssistedInject constructor(
                 )
                 val callback = object : ConfirmAccountProviderNode.Callback {
                     override fun onOidcDetails(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))
-                        }
+                        navigateToMas(oidcDetails)
                     }
 
                     override fun onCreateAccountContinue(url: String) {
@@ -222,6 +237,19 @@ 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))
+        }
+    }
+
     @Composable
     override fun View(modifier: Modifier) {
         activity = requireNotNull(LocalActivity.current)
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt
new file mode 100644
index 00000000000..2d401d45aa1
--- /dev/null
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.impl.login
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import io.element.android.features.login.impl.DefaultLoginUserStory
+import io.element.android.features.login.impl.error.ChangeServerError
+import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderPresenter
+import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
+import io.element.android.features.login.impl.screens.onboarding.OnBoardingPresenter
+import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
+import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.architecture.runCatchingUpdatingState
+import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
+import io.element.android.libraries.matrix.api.auth.OidcPrompt
+import io.element.android.libraries.oidc.api.OidcAction
+import io.element.android.libraries.oidc.api.OidcActionFlow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+/**
+ * This class is responsible for managing the login flow, including handling OIDC actions and
+ * submitting login requests.
+ * It's an helper to avoid code duplication. It is used by [OnBoardingPresenter] and [ConfirmAccountProviderPresenter].
+ */
+class LoginHelper @Inject constructor(
+    private val oidcActionFlow: OidcActionFlow,
+    private val authenticationService: MatrixAuthenticationService,
+    private val defaultLoginUserStory: DefaultLoginUserStory,
+    private val webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever,
+) {
+    private val loginModeState: MutableState<AsyncData<LoginMode>> = mutableStateOf(AsyncData.Uninitialized)
+
+    @Composable
+    fun collectLoginMode(): State<AsyncData<LoginMode>> {
+        LaunchedEffect(Unit) {
+            oidcActionFlow.collect { oidcAction ->
+                if (oidcAction != null) {
+                    onOidcAction(oidcAction)
+                }
+            }
+        }
+        return loginModeState
+    }
+
+    fun clearError() {
+        loginModeState.value = AsyncData.Uninitialized
+    }
+
+    fun submit(
+        coroutineScope: CoroutineScope,
+        isAccountCreation: Boolean,
+        homeserverUrl: String,
+        loginHint: String?,
+    ) = coroutineScope.launch {
+        suspend {
+            authenticationService.setHomeserver(homeserverUrl).map {
+                val matrixHomeServerDetails = authenticationService.getHomeserverDetails().value!!
+                if (matrixHomeServerDetails.supportsOidcLogin) {
+                    // Retrieve the details right now
+                    val oidcPrompt = if (isAccountCreation) OidcPrompt.Create else OidcPrompt.Login
+                    LoginMode.Oidc(
+                        authenticationService.getOidcUrl(prompt = oidcPrompt, loginHint = loginHint).getOrThrow()
+                    )
+                } else if (isAccountCreation) {
+                    val url = webClientUrlForAuthenticationRetriever.retrieve(homeserverUrl)
+                    LoginMode.AccountCreation(url)
+                } else if (matrixHomeServerDetails.supportsPasswordLogin) {
+                    LoginMode.PasswordLogin
+                } else {
+                    error("Unsupported login flow")
+                }
+            }.getOrThrow()
+        }.runCatchingUpdatingState(
+            state = loginModeState,
+            errorTransform = {
+                when (it) {
+                    is AccountCreationNotSupported -> it
+                    else -> ChangeServerError.from(it)
+                }
+            }
+        )
+    }
+
+    private suspend fun onOidcAction(oidcAction: OidcAction) {
+        loginModeState.value = AsyncData.Loading()
+        when (oidcAction) {
+            OidcAction.GoBack -> {
+                authenticationService.cancelOidcLogin()
+                    .onSuccess {
+                        loginModeState.value = AsyncData.Uninitialized
+                    }
+                    .onFailure { failure ->
+                        loginModeState.value = AsyncData.Failure(failure)
+                    }
+            }
+            is OidcAction.Success -> {
+                authenticationService.loginWithOidc(oidcAction.url)
+                    .onSuccess { _ ->
+                        defaultLoginUserStory.setLoginFlowIsDone(true)
+                    }
+                    .onFailure { failure ->
+                        loginModeState.value = AsyncData.Failure(failure)
+                    }
+            }
+        }
+        oidcActionFlow.reset()
+    }
+}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt
new file mode 100644
index 00000000000..571b0182977
--- /dev/null
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.impl.login
+
+import io.element.android.libraries.matrix.api.auth.OidcDetails
+
+sealed interface LoginMode {
+    data object PasswordLogin : LoginMode
+    data class Oidc(val oidcDetails: OidcDetails) : LoginMode
+    data class AccountCreation(val url: String) : LoginMode
+}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt
new file mode 100644
index 00000000000..b4fff3acab8
--- /dev/null
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.impl.login
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import io.element.android.features.login.impl.R
+import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
+import io.element.android.features.login.impl.error.ChangeServerError
+import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
+import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
+import io.element.android.libraries.designsystem.theme.LocalBuildMeta
+import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.ui.strings.CommonStrings
+
+@Composable
+fun LoginModeView(
+    loginMode: AsyncData<LoginMode>,
+    onClearError: () -> Unit,
+    onLearnMoreClick: () -> Unit,
+    onOidcDetails: (OidcDetails) -> Unit,
+    onNeedLoginPassword: () -> Unit,
+    onCreateAccountContinue: (url: String) -> Unit
+) {
+    when (loginMode) {
+        is AsyncData.Failure -> {
+            when (val error = loginMode.error) {
+                is ChangeServerError -> {
+                    when (error) {
+                        is ChangeServerError.Error -> {
+                            ErrorDialog(
+                                content = error.message(),
+                                onSubmit = onClearError,
+                            )
+                        }
+                        is ChangeServerError.SlidingSyncAlert -> {
+                            SlidingSyncNotSupportedDialog(
+                                onLearnMoreClick = {
+                                    onLearnMoreClick()
+                                    onClearError()
+                                },
+                                onDismiss = onClearError,
+                            )
+                        }
+                        is ChangeServerError.UnauthorizedAccountProvider -> {
+                            ErrorDialog(
+                                content = stringResource(
+                                    id = R.string.screen_change_server_error_unauthorized_homeserver,
+                                    LocalBuildMeta.current.applicationName,
+                                    error.unauthorisedAccountProviderTitle,
+                                ),
+                                onSubmit = onClearError,
+                            )
+                        }
+                    }
+                }
+                is AccountCreationNotSupported -> {
+                    ErrorDialog(
+                        content = stringResource(CommonStrings.error_account_creation_not_possible),
+                        onSubmit = onClearError,
+                    )
+                }
+                else -> {
+                    ErrorDialog(
+                        content = stringResource(CommonStrings.error_unknown),
+                        onSubmit = onClearError,
+                    )
+                }
+            }
+        }
+        is AsyncData.Loading -> Unit // The Continue button shows the loading state
+        is AsyncData.Success -> {
+            when (val loginModeData = loginMode.data) {
+                is LoginMode.Oidc -> onOidcDetails(loginModeData.oidcDetails)
+                LoginMode.PasswordLogin -> onNeedLoginPassword()
+                is LoginMode.AccountCreation -> onCreateAccountContinue(loginModeData.url)
+            }
+        }
+        AsyncData.Uninitialized -> Unit
+    }
+}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingPresenter.kt
deleted file mode 100644
index 3d4029990e9..00000000000
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingPresenter.kt
+++ /dev/null
@@ -1,44 +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.features.login.impl.onboarding
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.produceState
-import androidx.compose.runtime.remember
-import io.element.android.appconfig.OnBoardingConfig
-import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
-import io.element.android.libraries.architecture.Presenter
-import io.element.android.libraries.core.meta.BuildMeta
-import io.element.android.libraries.featureflag.api.FeatureFlagService
-import io.element.android.libraries.featureflag.api.FeatureFlags
-import javax.inject.Inject
-
-/**
- * Note: this Presenter is ignored regarding code coverage because it cannot reach the coverage threshold.
- * When this presenter get more code in it, please remove the ignore rule in the kover configuration.
- */
-class OnBoardingPresenter @Inject constructor(
-    private val buildMeta: BuildMeta,
-    private val featureFlagService: FeatureFlagService,
-    private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
-) : Presenter<OnBoardingState> {
-    @Composable
-    override fun present(): OnBoardingState {
-        val canLoginWithQrCode by produceState(initialValue = false) {
-            value = featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin)
-        }
-        val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() }
-        return OnBoardingState(
-            productionApplicationName = buildMeta.productionApplicationName,
-            canLoginWithQrCode = canLoginWithQrCode,
-            canCreateAccount = OnBoardingConfig.CAN_CREATE_ACCOUNT,
-            canReportBug = canReportBug,
-        )
-    }
-}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingState.kt
deleted file mode 100644
index f36566398a4..00000000000
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingState.kt
+++ /dev/null
@@ -1,15 +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.features.login.impl.onboarding
-
-data class OnBoardingState(
-    val productionApplicationName: String,
-    val canLoginWithQrCode: Boolean,
-    val canCreateAccount: Boolean,
-    val canReportBug: Boolean,
-)
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 99b029a9280..3bcc81ac831 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
@@ -8,38 +8,20 @@
 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
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-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.screens.createaccount.AccountCreationNotSupported
-import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
-import io.element.android.libraries.architecture.AsyncData
+import io.element.android.features.login.impl.login.LoginHelper
 import io.element.android.libraries.architecture.Presenter
-import io.element.android.libraries.architecture.runCatchingUpdatingState
-import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
-import io.element.android.libraries.matrix.api.auth.OidcPrompt
-import io.element.android.libraries.oidc.api.OidcAction
-import io.element.android.libraries.oidc.api.OidcActionFlow
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 
 class ConfirmAccountProviderPresenter @AssistedInject constructor(
     @Assisted private val params: Params,
     private val accountProviderDataSource: AccountProviderDataSource,
-    private val authenticationService: MatrixAuthenticationService,
-    private val oidcActionFlow: OidcActionFlow,
-    private val defaultLoginUserStory: DefaultLoginUserStory,
-    private val webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever,
+    private val loginHelper: LoginHelper,
 ) : Presenter<ConfirmAccountProviderState> {
     data class Params(
         val isAccountCreation: Boolean,
@@ -55,91 +37,27 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
         val accountProvider by accountProviderDataSource.flow.collectAsState()
         val localCoroutineScope = rememberCoroutineScope()
 
-        val loginFlowAction: MutableState<AsyncData<LoginFlow>> = remember {
-            mutableStateOf(AsyncData.Uninitialized)
-        }
-
-        LaunchedEffect(Unit) {
-            oidcActionFlow.collect { oidcAction ->
-                if (oidcAction != null) {
-                    onOidcAction(oidcAction, loginFlowAction)
-                }
-            }
-        }
+        val loginMode by loginHelper.collectLoginMode()
 
         fun handleEvents(event: ConfirmAccountProviderEvents) {
             when (event) {
                 ConfirmAccountProviderEvents.Continue -> {
-                    localCoroutineScope.submit(accountProvider.url, loginFlowAction)
+                    loginHelper.submit(
+                        coroutineScope = localCoroutineScope,
+                        isAccountCreation = params.isAccountCreation,
+                        homeserverUrl = accountProvider.url,
+                        loginHint = null,
+                    )
                 }
-                ConfirmAccountProviderEvents.ClearError -> loginFlowAction.value = AsyncData.Uninitialized
+                ConfirmAccountProviderEvents.ClearError -> loginHelper.clearError()
             }
         }
 
         return ConfirmAccountProviderState(
             accountProvider = accountProvider,
             isAccountCreation = params.isAccountCreation,
-            loginFlow = loginFlowAction.value,
+            loginMode = loginMode,
             eventSink = ::handleEvents
         )
     }
-
-    private fun CoroutineScope.submit(
-        homeserverUrl: String,
-        loginFlowAction: MutableState<AsyncData<LoginFlow>>,
-    ) = launch {
-        suspend {
-            authenticationService.setHomeserver(homeserverUrl).map {
-                val matrixHomeServerDetails = authenticationService.getHomeserverDetails().value!!
-                if (matrixHomeServerDetails.supportsOidcLogin) {
-                    // Retrieve the details right now
-                    val oidcPrompt = if (params.isAccountCreation) OidcPrompt.Create else OidcPrompt.Login
-                    LoginFlow.OidcFlow(authenticationService.getOidcUrl(oidcPrompt).getOrThrow())
-                } else if (params.isAccountCreation) {
-                    val url = webClientUrlForAuthenticationRetriever.retrieve(homeserverUrl)
-                    LoginFlow.AccountCreationFlow(url)
-                } else if (matrixHomeServerDetails.supportsPasswordLogin) {
-                    LoginFlow.PasswordLogin
-                } else {
-                    error("Unsupported login flow")
-                }
-            }.getOrThrow()
-        }.runCatchingUpdatingState(
-            state = loginFlowAction,
-            errorTransform = {
-                when (it) {
-                    is AccountCreationNotSupported -> it
-                    else -> ChangeServerError.from(it)
-                }
-            }
-        )
-    }
-
-    private suspend fun onOidcAction(
-        oidcAction: OidcAction,
-        loginFlowAction: MutableState<AsyncData<LoginFlow>>,
-    ) {
-        loginFlowAction.value = AsyncData.Loading()
-        when (oidcAction) {
-            OidcAction.GoBack -> {
-                authenticationService.cancelOidcLogin()
-                    .onSuccess {
-                        loginFlowAction.value = AsyncData.Uninitialized
-                    }
-                    .onFailure { failure ->
-                        loginFlowAction.value = AsyncData.Failure(failure)
-                    }
-            }
-            is OidcAction.Success -> {
-                authenticationService.loginWithOidc(oidcAction.url)
-                    .onSuccess { _ ->
-                        defaultLoginUserStory.setLoginFlowIsDone(true)
-                    }
-                    .onFailure { failure ->
-                        loginFlowAction.value = AsyncData.Failure(failure)
-                    }
-            }
-        }
-        oidcActionFlow.reset()
-    }
 }
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt
index aaf0d2562ac..d0da9ab85ef 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt
@@ -8,21 +8,15 @@
 package io.element.android.features.login.impl.screens.confirmaccountprovider
 
 import io.element.android.features.login.impl.accountprovider.AccountProvider
+import io.element.android.features.login.impl.login.LoginMode
 import io.element.android.libraries.architecture.AsyncData
-import io.element.android.libraries.matrix.api.auth.OidcDetails
 
 // Do not use default value, so no member get forgotten in the presenters.
 data class ConfirmAccountProviderState(
     val accountProvider: AccountProvider,
     val isAccountCreation: Boolean,
-    val loginFlow: AsyncData<LoginFlow>,
+    val loginMode: AsyncData<LoginMode>,
     val eventSink: (ConfirmAccountProviderEvents) -> Unit
 ) {
-    val submitEnabled: Boolean get() = accountProvider.url.isNotEmpty() && (loginFlow is AsyncData.Uninitialized || loginFlow is AsyncData.Loading)
-}
-
-sealed interface LoginFlow {
-    data object PasswordLogin : LoginFlow
-    data class OidcFlow(val oidcDetails: OidcDetails) : LoginFlow
-    data class AccountCreationFlow(val url: String) : LoginFlow
+    val submitEnabled: Boolean get() = accountProvider.url.isNotEmpty() && (loginMode is AsyncData.Uninitialized || loginMode is AsyncData.Loading)
 }
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt
index 3edf84f7c15..90d34571e9d 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt
@@ -10,6 +10,7 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider
 import androidx.compose.ui.tooling.preview.PreviewParameterProvider
 import io.element.android.features.login.impl.accountprovider.AccountProvider
 import io.element.android.features.login.impl.accountprovider.anAccountProvider
+import io.element.android.features.login.impl.login.LoginMode
 import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
 import io.element.android.libraries.architecture.AsyncData
 
@@ -22,7 +23,7 @@ open class ConfirmAccountProviderStateProvider : PreviewParameterProvider<Confir
             ),
             aConfirmAccountProviderState(
                 isAccountCreation = true,
-                loginFlow = AsyncData.Failure(AccountCreationNotSupported())
+                loginMode = AsyncData.Failure(AccountCreationNotSupported())
             ),
         )
 }
@@ -30,11 +31,11 @@ open class ConfirmAccountProviderStateProvider : PreviewParameterProvider<Confir
 private fun aConfirmAccountProviderState(
     accountProvider: AccountProvider = anAccountProvider(),
     isAccountCreation: Boolean = false,
-    loginFlow: AsyncData<LoginFlow> = AsyncData.Uninitialized,
+    loginMode: AsyncData<LoginMode> = AsyncData.Uninitialized,
     eventSink: (ConfirmAccountProviderEvents) -> Unit = {},
 ) = ConfirmAccountProviderState(
     accountProvider = accountProvider,
     isAccountCreation = isAccountCreation,
-    loginFlow = loginFlow,
+    loginMode = loginMode,
     eventSink = eventSink
 )
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt
index b4c21bb1a1c..32d6671452b 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt
@@ -19,15 +19,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
 import androidx.compose.ui.unit.dp
 import io.element.android.compound.tokens.generated.CompoundIcons
 import io.element.android.features.login.impl.R
-import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
-import io.element.android.features.login.impl.error.ChangeServerError
-import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
+import io.element.android.features.login.impl.login.LoginModeView
 import io.element.android.libraries.architecture.AsyncData
 import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
 import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
 import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
 import io.element.android.libraries.designsystem.components.BigIcon
-import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
 import io.element.android.libraries.designsystem.preview.ElementPreview
 import io.element.android.libraries.designsystem.preview.PreviewsDayNight
 import io.element.android.libraries.designsystem.theme.components.Button
@@ -47,9 +44,9 @@ fun ConfirmAccountProviderView(
     onChange: () -> Unit,
     modifier: Modifier = Modifier,
 ) {
-    val isLoading by remember(state.loginFlow) {
+    val isLoading by remember(state.loginMode) {
         derivedStateOf {
-            state.loginFlow is AsyncData.Loading
+            state.loginMode is AsyncData.Loading
         }
     }
     val eventSink = state.eventSink
@@ -99,48 +96,16 @@ fun ConfirmAccountProviderView(
             }
         }
     ) {
-        when (state.loginFlow) {
-            is AsyncData.Failure -> {
-                when (val error = state.loginFlow.error) {
-                    is ChangeServerError.Error -> {
-                        ErrorDialog(
-                            content = error.message(),
-                            onSubmit = {
-                                eventSink.invoke(ConfirmAccountProviderEvents.ClearError)
-                            }
-                        )
-                    }
-                    is ChangeServerError.SlidingSyncAlert -> {
-                        SlidingSyncNotSupportedDialog(
-                            onLearnMoreClick = {
-                                onLearnMoreClick()
-                                eventSink(ConfirmAccountProviderEvents.ClearError)
-                            },
-                            onDismiss = {
-                                eventSink(ConfirmAccountProviderEvents.ClearError)
-                            }
-                        )
-                    }
-                    is AccountCreationNotSupported -> {
-                        ErrorDialog(
-                            content = stringResource(CommonStrings.error_account_creation_not_possible),
-                            onSubmit = {
-                                eventSink.invoke(ConfirmAccountProviderEvents.ClearError)
-                            }
-                        )
-                    }
-                }
-            }
-            is AsyncData.Loading -> Unit // The Continue button shows the loading state
-            is AsyncData.Success -> {
-                when (val loginFlowState = state.loginFlow.data) {
-                    is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails)
-                    LoginFlow.PasswordLogin -> onNeedLoginPassword()
-                    is LoginFlow.AccountCreationFlow -> onCreateAccountContinue(loginFlowState.url)
-                }
-            }
-            AsyncData.Uninitialized -> Unit
-        }
+        LoginModeView(
+            loginMode = state.loginMode,
+            onClearError = {
+                eventSink(ConfirmAccountProviderEvents.ClearError)
+            },
+            onLearnMoreClick = onLearnMoreClick,
+            onOidcDetails = onOidcDetails,
+            onNeedLoginPassword = onNeedLoginPassword,
+            onCreateAccountContinue = onCreateAccountContinue,
+        )
     }
 }
 
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt
new file mode 100644
index 00000000000..6ea5bfa488e
--- /dev/null
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.impl.screens.onboarding
+
+sealed interface OnBoardingEvents {
+    data class OnSignIn(
+        val defaultAccountProvider: String
+    ) : OnBoardingEvents
+
+    data object ClearError : OnBoardingEvents
+}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt
similarity index 55%
rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingNode.kt
rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt
index 0f566ea1d36..f17372b3aba 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingNode.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt
@@ -5,10 +5,11 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-package io.element.android.features.login.impl.onboarding
+package io.element.android.features.login.impl.screens.onboarding
 
 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
@@ -16,13 +17,17 @@ 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.features.login.impl.util.openLearnMorePage
+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
 
 @ContributesNode(AppScope::class)
 class OnBoardingNode @AssistedInject constructor(
     @Assisted buildContext: BuildContext,
     @Assisted plugins: List<Plugin>,
-    private val presenter: OnBoardingPresenter,
+    presenterFactory: OnBoardingPresenter.Factory,
 ) : Node(
     buildContext = buildContext,
     plugins = plugins
@@ -32,8 +37,22 @@ class OnBoardingNode @AssistedInject constructor(
         fun onSignIn()
         fun onSignInWithQrCode()
         fun onReportProblem()
+        fun onLoginPasswordNeeded()
+        fun onOidcDetails(oidcDetails: OidcDetails)
+        fun onCreateAccountContinue(url: String)
     }
 
+    data class Params(
+        val accountProvider: String?,
+        val loginHint: String?,
+    ) : NodeInputs
+
+    private val params = inputs<Params>()
+
+    private val presenter = presenterFactory.create(
+        params = params,
+    )
+
     private fun onSignIn() {
         plugins<Callback>().forEach { it.onSignIn() }
     }
@@ -50,9 +69,22 @@ class OnBoardingNode @AssistedInject constructor(
         plugins<Callback>().forEach { it.onReportProblem() }
     }
 
+    private fun onOidcDetails(data: OidcDetails) {
+        plugins<Callback>().forEach { it.onOidcDetails(data) }
+    }
+
+    private fun onLoginPasswordNeeded() {
+        plugins<Callback>().forEach { it.onLoginPasswordNeeded() }
+    }
+
+    private fun onCreateAccountContinue(url: String) {
+        plugins<Callback>().forEach { it.onCreateAccountContinue(url) }
+    }
+
     @Composable
     override fun View(modifier: Modifier) {
         val state = presenter.present()
+        val context = LocalContext.current
         OnBoardingView(
             state = state,
             modifier = modifier,
@@ -60,6 +92,10 @@ class OnBoardingNode @AssistedInject constructor(
             onCreateAccount = ::onSignUp,
             onSignInWithQrCode = ::onSignInWithQrCode,
             onReportProblem = ::onReportProblem,
+            onOidcDetails = ::onOidcDetails,
+            onNeedLoginPassword = ::onLoginPasswordNeeded,
+            onLearnMoreClick = { openLearnMorePage(context) },
+            onCreateAccountContinue = ::onCreateAccountContinue,
         )
     }
 }
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt
new file mode 100644
index 00000000000..38ea9c3b499
--- /dev/null
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.features.login.impl.screens.onboarding
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import io.element.android.appconfig.OnBoardingConfig
+import io.element.android.features.login.impl.login.LoginHelper
+import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
+import io.element.android.libraries.architecture.Presenter
+import io.element.android.libraries.core.meta.BuildMeta
+import io.element.android.libraries.featureflag.api.FeatureFlagService
+import io.element.android.libraries.featureflag.api.FeatureFlags
+
+class OnBoardingPresenter @AssistedInject constructor(
+    @Assisted private val params: OnBoardingNode.Params,
+    private val buildMeta: BuildMeta,
+    private val featureFlagService: FeatureFlagService,
+    private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
+    private val loginHelper: LoginHelper,
+) : Presenter<OnBoardingState> {
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            params: OnBoardingNode.Params,
+        ): OnBoardingPresenter
+    }
+
+    private val defaultAccountProvider = params.accountProvider
+    private val loginHint = params.loginHint
+
+    @Composable
+    override fun present(): OnBoardingState {
+        val localCoroutineScope = rememberCoroutineScope()
+
+        val canLoginWithQrCode by produceState(initialValue = false) {
+            value = defaultAccountProvider == null &&
+                featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin)
+        }
+        val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() }
+
+        val loginMode by loginHelper.collectLoginMode()
+
+        fun handleEvent(event: OnBoardingEvents) {
+            when (event) {
+                is OnBoardingEvents.OnSignIn -> loginHelper.submit(
+                    coroutineScope = localCoroutineScope,
+                    isAccountCreation = false,
+                    homeserverUrl = event.defaultAccountProvider,
+                    loginHint = loginHint,
+                )
+                OnBoardingEvents.ClearError -> loginHelper.clearError()
+            }
+        }
+
+        return OnBoardingState(
+            productionApplicationName = buildMeta.productionApplicationName,
+            defaultAccountProvider = defaultAccountProvider,
+            canLoginWithQrCode = canLoginWithQrCode,
+            canCreateAccount = defaultAccountProvider == null && OnBoardingConfig.CAN_CREATE_ACCOUNT,
+            canReportBug = canReportBug,
+            loginMode = loginMode,
+            eventSink = ::handleEvent,
+        )
+    }
+}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt
new file mode 100644
index 00000000000..ded9ae71026
--- /dev/null
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.features.login.impl.screens.onboarding
+
+import io.element.android.features.login.impl.login.LoginMode
+import io.element.android.libraries.architecture.AsyncData
+
+data class OnBoardingState(
+    val productionApplicationName: String,
+    val defaultAccountProvider: String?,
+    val canLoginWithQrCode: Boolean,
+    val canCreateAccount: Boolean,
+    val canReportBug: Boolean,
+    val loginMode: AsyncData<LoginMode>,
+    val eventSink: (OnBoardingEvents) -> Unit,
+) {
+    val submitEnabled: Boolean
+        get() = defaultAccountProvider != null && (loginMode is AsyncData.Uninitialized || loginMode is AsyncData.Loading)
+}
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt
similarity index 67%
rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingStateProvider.kt
rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt
index 26135ad3532..975f9d2e7f6 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingStateProvider.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt
@@ -5,9 +5,11 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-package io.element.android.features.login.impl.onboarding
+package io.element.android.features.login.impl.screens.onboarding
 
 import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import io.element.android.features.login.impl.login.LoginMode
+import io.element.android.libraries.architecture.AsyncData
 
 open class OnBoardingStateProvider : PreviewParameterProvider<OnBoardingState> {
     override val values: Sequence<OnBoardingState>
@@ -17,17 +19,24 @@ open class OnBoardingStateProvider : PreviewParameterProvider<OnBoardingState> {
             anOnBoardingState(canCreateAccount = true),
             anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true),
             anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true, canReportBug = true),
+            anOnBoardingState(defaultAccountProvider = "element.io", canCreateAccount = false, canReportBug = true),
         )
 }
 
 fun anOnBoardingState(
     productionApplicationName: String = "Element",
+    defaultAccountProvider: String? = null,
     canLoginWithQrCode: Boolean = false,
     canCreateAccount: Boolean = false,
     canReportBug: Boolean = false,
+    loginMode: AsyncData<LoginMode> = AsyncData.Uninitialized,
+    eventSink: (OnBoardingEvents) -> Unit = {},
 ) = OnBoardingState(
     productionApplicationName = productionApplicationName,
+    defaultAccountProvider = defaultAccountProvider,
     canLoginWithQrCode = canLoginWithQrCode,
     canCreateAccount = canCreateAccount,
     canReportBug = canReportBug,
+    loginMode = loginMode,
+    eventSink = eventSink,
 )
diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt
similarity index 75%
rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingView.kt
rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt
index bfe13487648..dd41e38e649 100644
--- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingView.kt
+++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt
@@ -5,7 +5,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-package io.element.android.features.login.impl.onboarding
+package io.element.android.features.login.impl.screens.onboarding
 
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
@@ -16,6 +16,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment.Companion.CenterHorizontally
 import androidx.compose.ui.BiasAlignment
 import androidx.compose.ui.Modifier
@@ -27,6 +30,8 @@ import androidx.compose.ui.unit.sp
 import io.element.android.compound.theme.ElementTheme
 import io.element.android.compound.tokens.generated.CompoundIcons
 import io.element.android.features.login.impl.R
+import io.element.android.features.login.impl.login.LoginModeView
+import io.element.android.libraries.architecture.AsyncData
 import io.element.android.libraries.designsystem.atomic.atoms.ElementLogoAtom
 import io.element.android.libraries.designsystem.atomic.atoms.ElementLogoAtomSize
 import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
@@ -37,6 +42,7 @@ import io.element.android.libraries.designsystem.theme.components.Button
 import io.element.android.libraries.designsystem.theme.components.IconSource
 import io.element.android.libraries.designsystem.theme.components.Text
 import io.element.android.libraries.designsystem.theme.components.TextButton
+import io.element.android.libraries.matrix.api.auth.OidcDetails
 import io.element.android.libraries.testtags.TestTags
 import io.element.android.libraries.testtags.testTag
 import io.element.android.libraries.ui.strings.CommonStrings
@@ -52,6 +58,10 @@ fun OnBoardingView(
     onSignInWithQrCode: () -> Unit,
     onSignIn: () -> Unit,
     onCreateAccount: () -> Unit,
+    onOidcDetails: (OidcDetails) -> Unit,
+    onNeedLoginPassword: () -> Unit,
+    onLearnMoreClick: () -> Unit,
+    onCreateAccountContinue: (url: String) -> Unit,
     onReportProblem: () -> Unit,
     modifier: Modifier = Modifier,
 ) {
@@ -59,6 +69,16 @@ fun OnBoardingView(
         modifier = modifier,
         content = {
             OnBoardingContent(state = state)
+            LoginModeView(
+                loginMode = state.loginMode,
+                onClearError = {
+                    state.eventSink(OnBoardingEvents.ClearError)
+                },
+                onLearnMoreClick = onLearnMoreClick,
+                onOidcDetails = onOidcDetails,
+                onNeedLoginPassword = onNeedLoginPassword,
+                onCreateAccountContinue = onCreateAccountContinue,
+            )
         },
         footer = {
             OnBoardingButtons(
@@ -127,6 +147,12 @@ private fun OnBoardingButtons(
     onCreateAccount: () -> Unit,
     onReportProblem: () -> Unit,
 ) {
+    val isLoading by remember(state.loginMode) {
+        derivedStateOf {
+            state.loginMode is AsyncData.Loading
+        }
+    }
+
     ButtonColumnMolecule {
         val signInButtonStringRes = if (state.canLoginWithQrCode || state.canCreateAccount) {
             R.string.screen_onboarding_sign_in_manually
@@ -141,13 +167,27 @@ private fun OnBoardingButtons(
                 modifier = Modifier.fillMaxWidth()
             )
         }
-        Button(
-            text = stringResource(id = signInButtonStringRes),
-            onClick = onSignIn,
-            modifier = Modifier
-                .fillMaxWidth()
-                .testTag(TestTags.onBoardingSignIn)
-        )
+        val defaultAccountProvider = state.defaultAccountProvider
+        if (defaultAccountProvider == null) {
+            Button(
+                text = stringResource(id = signInButtonStringRes),
+                onClick = onSignIn,
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .testTag(TestTags.onBoardingSignIn)
+            )
+        } else {
+            Button(
+                text = stringResource(id = R.string.screen_onboarding_sign_in_to, defaultAccountProvider),
+                showProgress = isLoading,
+                onClick = {
+                    state.eventSink(OnBoardingEvents.OnSignIn(defaultAccountProvider))
+                },
+                enabled = state.submitEnabled || isLoading,
+                modifier = Modifier
+                    .fillMaxWidth()
+            )
+        }
         if (state.canCreateAccount) {
             TextButton(
                 text = stringResource(id = R.string.screen_onboarding_sign_up),
@@ -181,5 +221,9 @@ internal fun OnBoardingViewPreview(
         onSignIn = {},
         onCreateAccount = {},
         onReportProblem = {},
+        onOidcDetails = {},
+        onNeedLoginPassword = {},
+        onLearnMoreClick = {},
+        onCreateAccountContinue = {},
     )
 }
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt
index d423c231b0f..d1c47ae663c 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt
@@ -43,7 +43,10 @@ interface MatrixAuthenticationService {
     /**
      * Get the Oidc url to display to the user.
      */
-    suspend fun getOidcUrl(prompt: OidcPrompt): Result<OidcDetails>
+    suspend fun getOidcUrl(
+        prompt: OidcPrompt,
+        loginHint: String?,
+    ): Result<OidcDetails>
 
     /**
      * Cancel Oidc login sequence.
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 5e0c3058c9b..8f7135a9143 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
@@ -187,14 +187,17 @@ class RustMatrixAuthenticationService @Inject constructor(
 
     private var pendingOAuthAuthorizationData: OAuthAuthorizationData? = null
 
-    override suspend fun getOidcUrl(prompt: OidcPrompt): Result<OidcDetails> {
+    override suspend fun getOidcUrl(
+        prompt: OidcPrompt,
+        loginHint: String?,
+    ): Result<OidcDetails> {
         return withContext(coroutineDispatchers.io) {
             runCatching {
                 val client = currentClient ?: error("You need to call `setHomeserver()` first")
                 val oAuthAuthorizationData = client.urlForOidc(
                     oidcConfiguration = oidcConfigurationProvider.get(),
                     prompt = prompt.toRustPrompt(),
-                    loginHint = null,
+                    loginHint = loginHint,
                 )
                 val url = oAuthAuthorizationData.loginUrl()
                 pendingOAuthAuthorizationData = oAuthAuthorizationData
diff --git a/tools/adb/deeplink_mobile.sh b/tools/adb/deeplink_mobile.sh
new file mode 100755
index 00000000000..9f680238cd8
--- /dev/null
+++ b/tools/adb/deeplink_mobile.sh
@@ -0,0 +1,12 @@
+#! /bin/bash
+
+# Copyright 2025 New Vector Ltd.
+#
+# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+# Please see LICENSE files in the repository root for full details.
+
+# Format is:
+# https://mobile.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org
+
+adb shell am start -a android.intent.action.VIEW \
+    -d "https://mobile.element.io/element?account_provider=element.io\\&login_hint=mxid:@alice:element.io"


Test patch:
diff --git a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt
index 46c54a98d04..3fe9a9b728e 100644
--- a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt
+++ b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt
@@ -12,6 +12,8 @@ import android.content.Intent
 import android.net.Uri
 import androidx.core.net.toUri
 import com.google.common.truth.Truth.assertThat
+import io.element.android.features.login.api.LoginParams
+import io.element.android.features.login.test.FakeLoginIntentResolver
 import io.element.android.libraries.deeplink.DeepLinkCreator
 import io.element.android.libraries.deeplink.DeeplinkData
 import io.element.android.libraries.deeplink.DeeplinkParser
@@ -165,6 +167,7 @@ class IntentResolverTest {
             userId = UserId("@alice:matrix.org")
         )
         val sut = createIntentResolver(
+            loginIntentResolverResult = { null },
             permalinkParserResult = { permalinkData }
         )
         val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
@@ -182,7 +185,8 @@ class IntentResolverTest {
     @Test
     fun `test resolve external permalink, FallbackLink should be ignored`() {
         val sut = createIntentResolver(
-            permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) }
+            permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) },
+            loginIntentResolverResult = { null },
         )
         val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
             action = Intent.ACTION_VIEW
@@ -231,7 +235,8 @@ class IntentResolverTest {
     @Test
     fun `test resolve invalid`() {
         val sut = createIntentResolver(
-            permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) }
+            permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) },
+            loginIntentResolverResult = { null },
         )
         val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
             action = Intent.ACTION_VIEW
@@ -241,11 +246,29 @@ class IntentResolverTest {
         assertThat(result).isNull()
     }
 
+    @Test
+    fun `test resolve login param`() {
+        val aLoginParams = LoginParams("accountProvider", null)
+        val sut = createIntentResolver(
+            loginIntentResolverResult = { aLoginParams },
+        )
+        val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
+            action = Intent.ACTION_VIEW
+            data = "".toUri()
+        }
+        val result = sut.resolve(intent)
+        assertThat(result).isEqualTo(ResolvedIntent.Login(aLoginParams))
+    }
+
     private fun createIntentResolver(
-        permalinkParserResult: (String) -> PermalinkData = { lambdaError() }
+        permalinkParserResult: (String) -> PermalinkData = { lambdaError() },
+        loginIntentResolverResult: (String) -> LoginParams? = { lambdaError() },
     ): IntentResolver {
         return IntentResolver(
             deeplinkParser = DeeplinkParser(),
+            loginIntentResolver = FakeLoginIntentResolver(
+                parseResult = loginIntentResolverResult,
+            ),
             oidcIntentResolver = DefaultOidcIntentResolver(
                 oidcUrlParser = DefaultOidcUrlParser(
                     oidcRedirectUrlProvider = FakeOidcRedirectUrlProvider(),
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolverTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolverTest.kt
new file mode 100644
index 00000000000..69d82e410d2
--- /dev/null
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolverTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.impl
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import io.element.android.features.login.api.LoginParams
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class DefaultLoginIntentResolverTest {
+    @Test
+    fun `nominal case`() {
+        val sut = DefaultLoginIntentResolver()
+        val uriString = "https://mobile.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org"
+        assertThat(sut.parse(uriString)).isEqualTo(
+            LoginParams(
+                accountProvider = "example.org",
+                loginHint = "mxid:@alice:example.org",
+            )
+        )
+    }
+
+    @Test
+    fun `extra unknown param`() {
+        val sut = DefaultLoginIntentResolver()
+        val uriString = "https://mobile.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org&extra=uknown"
+        assertThat(sut.parse(uriString)).isEqualTo(
+            LoginParams(
+                accountProvider = "example.org",
+                loginHint = "mxid:@alice:example.org",
+            )
+        )
+    }
+
+    @Test
+    fun `no account provider`() {
+        val sut = DefaultLoginIntentResolver()
+        val uriString = "https://mobile.element.io/element?login_hint=mxid:@alice:example.org"
+        assertThat(sut.parse(uriString)).isNull()
+    }
+
+    @Test
+    fun `no path`() {
+        val sut = DefaultLoginIntentResolver()
+        val uriString = "https://mobile.element.io?account_provider=example.org&login_hint=mxid:@alice:example.org"
+        assertThat(sut.parse(uriString)).isNull()
+    }
+
+    @Test
+    fun `wrong path`() {
+        val sut = DefaultLoginIntentResolver()
+        val uriString = "https://mobile.element.io/wrong?account_provider=example.org&login_hint=mxid:@alice:example.org"
+        assertThat(sut.parse(uriString)).isNull()
+    }
+
+    @Test
+    fun `wrong host`() {
+        val sut = DefaultLoginIntentResolver()
+        val uriString = "https://wrong.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org"
+        assertThat(sut.parse(uriString)).isNull()
+    }
+
+    @Test
+    fun `no login_hint param`() {
+        val sut = DefaultLoginIntentResolver()
+        val uriString = "https://mobile.element.io/element?account_provider=example.org"
+        assertThat(sut.parse(uriString)).isEqualTo(
+            LoginParams(
+                accountProvider = "example.org",
+                loginHint = null,
+            )
+        )
+    }
+}
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingPresenterTest.kt
deleted file mode 100644
index faee90ca142..00000000000
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/onboarding/OnBoardingPresenterTest.kt
+++ /dev/null
@@ -1,68 +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.features.login.impl.onboarding
-
-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.appconfig.OnBoardingConfig
-import io.element.android.libraries.featureflag.api.FeatureFlags
-import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
-import io.element.android.libraries.matrix.test.core.aBuildMeta
-import io.element.android.tests.testutils.WarmUpRule
-import io.element.android.tests.testutils.test
-import kotlinx.coroutines.test.runTest
-import org.junit.Rule
-import org.junit.Test
-
-class OnBoardingPresenterTest {
-    @get:Rule
-    val warmUpRule = WarmUpRule()
-
-    @Test
-    fun `present - initial state`() = runTest {
-        val buildMeta = aBuildMeta(
-            applicationName = "A",
-            productionApplicationName = "B",
-            desktopApplicationName = "C",
-        )
-        val featureFlagService = FakeFeatureFlagService(
-            initialState = mapOf(FeatureFlags.QrCodeLogin.key to true),
-            buildMeta = buildMeta,
-        )
-        val presenter = OnBoardingPresenter(
-            buildMeta = buildMeta,
-            featureFlagService = featureFlagService,
-            rageshakeFeatureAvailability = { true },
-        )
-        moleculeFlow(RecompositionMode.Immediate) {
-            presenter.present()
-        }.test {
-            val initialState = awaitItem()
-            assertThat(initialState.canLoginWithQrCode).isFalse()
-            assertThat(initialState.productionApplicationName).isEqualTo("B")
-            assertThat(initialState.canCreateAccount).isEqualTo(OnBoardingConfig.CAN_CREATE_ACCOUNT)
-            assertThat(initialState.canReportBug).isTrue()
-            assertThat(awaitItem().canLoginWithQrCode).isTrue()
-        }
-    }
-
-    @Test
-    fun `present - rageshake not available`() = runTest {
-        val presenter = OnBoardingPresenter(
-            buildMeta = aBuildMeta(),
-            featureFlagService = FakeFeatureFlagService(),
-            rageshakeFeatureAvailability = { false },
-        )
-        presenter.test {
-            skipItems(1)
-            assertThat(awaitItem().canReportBug).isFalse()
-        }
-    }
-}
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 08eea148a6c..e8be30c1276 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
@@ -15,7 +15,9 @@ import io.element.android.appconfig.AuthenticationConfig
 import io.element.android.features.enterprise.test.FakeEnterpriseService
 import io.element.android.features.login.impl.DefaultLoginUserStory
 import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
+import io.element.android.features.login.impl.login.LoginMode
 import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
+import io.element.android.features.login.impl.screens.onboarding.createLoginHelper
 import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
 import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
 import io.element.android.libraries.architecture.AsyncData
@@ -25,6 +27,7 @@ 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.FakeMatrixAuthenticationService
 import io.element.android.libraries.oidc.api.OidcAction
+import io.element.android.libraries.oidc.api.OidcActionFlow
 import io.element.android.libraries.oidc.impl.customtab.DefaultOidcActionFlow
 import io.element.android.tests.testutils.WarmUpRule
 import io.element.android.tests.testutils.waitForPredicate
@@ -46,7 +49,7 @@ class ConfirmAccountProviderPresenterTest {
             assertThat(initialState.isAccountCreation).isFalse()
             assertThat(initialState.submitEnabled).isTrue()
             assertThat(initialState.accountProvider.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
-            assertThat(initialState.loginFlow).isEqualTo(AsyncData.Uninitialized)
+            assertThat(initialState.loginMode).isEqualTo(AsyncData.Uninitialized)
         }
     }
 
@@ -64,11 +67,11 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
             val loadingState = awaitItem()
             assertThat(loadingState.submitEnabled).isTrue()
-            assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
+            assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
             val successState = awaitItem()
             assertThat(successState.submitEnabled).isFalse()
-            assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
-            assertThat(successState.loginFlow.dataOrNull()).isEqualTo(LoginFlow.PasswordLogin)
+            assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
+            assertThat(successState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin)
         }
     }
 
@@ -86,11 +89,11 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
             val loadingState = awaitItem()
             assertThat(loadingState.submitEnabled).isTrue()
-            assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
+            assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
             val successState = awaitItem()
             assertThat(successState.submitEnabled).isFalse()
-            assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
-            assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
+            assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
+            assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
         }
     }
 
@@ -110,15 +113,15 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
             val loadingState = awaitItem()
             assertThat(loadingState.submitEnabled).isTrue()
-            assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
+            assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
             val successState = awaitItem()
             assertThat(successState.submitEnabled).isFalse()
-            assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
-            assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
+            assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
+            assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
             authenticationService.givenOidcCancelError(A_THROWABLE)
             defaultOidcActionFlow.post(OidcAction.GoBack)
             val cancelFailureState = awaitItem()
-            assertThat(cancelFailureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java)
+            assertThat(cancelFailureState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
         }
     }
 
@@ -138,14 +141,14 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
             val loadingState = awaitItem()
             assertThat(loadingState.submitEnabled).isTrue()
-            assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
+            assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
             val successState = awaitItem()
             assertThat(successState.submitEnabled).isFalse()
-            assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
-            assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
+            assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
+            assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
             defaultOidcActionFlow.post(OidcAction.GoBack)
             val cancelFinalState = awaitItem()
-            assertThat(cancelFinalState.loginFlow).isInstanceOf(AsyncData.Uninitialized::class.java)
+            assertThat(cancelFinalState.loginMode).isInstanceOf(AsyncData.Uninitialized::class.java)
         }
     }
 
@@ -165,17 +168,17 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
             val loadingState = awaitItem()
             assertThat(loadingState.submitEnabled).isTrue()
-            assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
+            assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
             val successState = awaitItem()
             assertThat(successState.submitEnabled).isFalse()
-            assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
-            assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
+            assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
+            assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
             authenticationService.givenLoginError(A_THROWABLE)
             defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
             val cancelLoadingState = awaitItem()
-            assertThat(cancelLoadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
+            assertThat(cancelLoadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
             val cancelFailureState = awaitItem()
-            assertThat(cancelFailureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java)
+            assertThat(cancelFailureState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
         }
     }
 
@@ -199,15 +202,15 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
             val loadingState = awaitItem()
             assertThat(loadingState.submitEnabled).isTrue()
-            assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
+            assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
             val successState = awaitItem()
             assertThat(successState.submitEnabled).isFalse()
-            assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
-            assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
+            assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
+            assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
             assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse()
             defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
             val successSuccessState = awaitItem()
-            assertThat(successSuccessState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
+            assertThat(successSuccessState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
             waitForPredicate { defaultLoginUserStory.loginFlowIsDone.value }
         }
     }
@@ -227,7 +230,7 @@ class ConfirmAccountProviderPresenterTest {
             skipItems(1) // Loading
             val failureState = awaitItem()
             assertThat(failureState.submitEnabled).isFalse()
-            assertThat(failureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java)
+            assertThat(failureState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
         }
     }
 
@@ -250,12 +253,12 @@ class ConfirmAccountProviderPresenterTest {
 
             // Check an error was returned
             val submittedState = awaitItem()
-            assertThat(submittedState.loginFlow).isInstanceOf(AsyncData.Failure::class.java)
+            assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
 
             // Assert the error is then cleared
             submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
             val clearedState = awaitItem()
-            assertThat(clearedState.loginFlow).isEqualTo(AsyncData.Uninitialized)
+            assertThat(clearedState.loginMode).isEqualTo(AsyncData.Uninitialized)
         }
     }
 
@@ -278,11 +281,11 @@ class ConfirmAccountProviderPresenterTest {
             skipItems(1) // Loading
             // Check an error was returned
             val submittedState = awaitItem()
-            assertThat(submittedState.loginFlow.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
+            assertThat(submittedState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
             // Assert the error is then cleared
             submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
             val clearedState = awaitItem()
-            assertThat(clearedState.loginFlow).isEqualTo(AsyncData.Uninitialized)
+            assertThat(clearedState.loginMode).isEqualTo(AsyncData.Uninitialized)
         }
     }
 
@@ -301,8 +304,8 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink(ConfirmAccountProviderEvents.Continue)
             skipItems(1) // Loading
             val submittedState = awaitItem()
-            assertThat(submittedState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
-            assertThat(submittedState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
+            assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Success::class.java)
+            assertThat(submittedState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
         }
     }
 
@@ -323,8 +326,8 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink(ConfirmAccountProviderEvents.Continue)
             skipItems(1) // Loading
             val submittedState = awaitItem()
-            assertThat(submittedState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
-            assertThat(submittedState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
+            assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Success::class.java)
+            assertThat(submittedState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
         }
     }
 
@@ -345,7 +348,7 @@ class ConfirmAccountProviderPresenterTest {
             initialState.eventSink(ConfirmAccountProviderEvents.Continue)
             skipItems(1) // Loading
             val submittedState = awaitItem()
-            assertThat(submittedState.loginFlow.dataOrNull()).isEqualTo(LoginFlow.AccountCreationFlow(aUrl))
+            assertThat(submittedState.loginMode.dataOrNull()).isEqualTo(LoginMode.AccountCreation(aUrl))
         }
     }
 
@@ -353,15 +356,17 @@ class ConfirmAccountProviderPresenterTest {
         params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
         accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
         matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
-        defaultOidcActionFlow: DefaultOidcActionFlow = DefaultOidcActionFlow(),
+        defaultOidcActionFlow: OidcActionFlow = DefaultOidcActionFlow(),
         defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(),
         webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
     ) = ConfirmAccountProviderPresenter(
         params = params,
         accountProviderDataSource = accountProviderDataSource,
-        authenticationService = matrixAuthenticationService,
-        oidcActionFlow = defaultOidcActionFlow,
-        defaultLoginUserStory = defaultLoginUserStory,
-        webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever
+        loginHelper = createLoginHelper(
+            authenticationService = matrixAuthenticationService,
+            oidcActionFlow = defaultOidcActionFlow,
+            defaultLoginUserStory = defaultLoginUserStory,
+            webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
+        ),
     )
 }
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt
new file mode 100644
index 00000000000..06034fef199
--- /dev/null
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.features.login.impl.screens.onboarding
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.appconfig.OnBoardingConfig
+import io.element.android.features.login.impl.DefaultLoginUserStory
+import io.element.android.features.login.impl.login.LoginHelper
+import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
+import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
+import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.core.meta.BuildMeta
+import io.element.android.libraries.featureflag.api.FeatureFlagService
+import io.element.android.libraries.featureflag.api.FeatureFlags
+import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
+import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
+import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
+import io.element.android.libraries.matrix.test.A_LOGIN_HINT
+import io.element.android.libraries.matrix.test.A_THROWABLE
+import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
+import io.element.android.libraries.matrix.test.core.aBuildMeta
+import io.element.android.libraries.oidc.api.OidcActionFlow
+import io.element.android.libraries.oidc.impl.customtab.DefaultOidcActionFlow
+import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.test
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+class OnBoardingPresenterTest {
+    @get:Rule
+    val warmUpRule = WarmUpRule()
+
+    @Test
+    fun `present - initial state`() = runTest {
+        val buildMeta = aBuildMeta(
+            applicationName = "A",
+            productionApplicationName = "B",
+            desktopApplicationName = "C",
+        )
+        val featureFlagService = FakeFeatureFlagService(
+            initialState = mapOf(FeatureFlags.QrCodeLogin.key to true),
+            buildMeta = buildMeta,
+        )
+        val presenter = createPresenter(
+            buildMeta = buildMeta,
+            featureFlagService = featureFlagService,
+            rageshakeFeatureAvailability = { true },
+        )
+        presenter.test {
+            val initialState = awaitItem()
+            assertThat(initialState.canLoginWithQrCode).isFalse()
+            assertThat(initialState.productionApplicationName).isEqualTo("B")
+            assertThat(initialState.canCreateAccount).isEqualTo(OnBoardingConfig.CAN_CREATE_ACCOUNT)
+            assertThat(initialState.canReportBug).isTrue()
+            assertThat(awaitItem().canLoginWithQrCode).isTrue()
+        }
+    }
+
+    @Test
+    fun `present - rageshake not available`() = runTest {
+        val presenter = createPresenter(
+            rageshakeFeatureAvailability = { false },
+        )
+        presenter.test {
+            skipItems(1)
+            assertThat(awaitItem().canReportBug).isFalse()
+        }
+    }
+
+    @Test
+    fun `present - default account provider`() = runTest {
+        val presenter = createPresenter(
+            params = OnBoardingNode.Params(
+                accountProvider = A_HOMESERVER_URL,
+                loginHint = null,
+            ),
+        )
+        presenter.test {
+            awaitItem().also {
+                assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL)
+                assertThat(it.canLoginWithQrCode).isFalse()
+                assertThat(it.canCreateAccount).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun `present - default account provider - login and clear error`() = runTest {
+        val authenticationService = FakeMatrixAuthenticationService()
+        val presenter = createPresenter(
+            params = OnBoardingNode.Params(
+                accountProvider = A_HOMESERVER_URL,
+                loginHint = A_LOGIN_HINT,
+            ),
+            loginHelper = createLoginHelper(
+                authenticationService = authenticationService,
+            ),
+        )
+        presenter.test {
+            awaitItem().also {
+                assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL)
+                authenticationService.givenChangeServerError(A_THROWABLE)
+                it.eventSink(OnBoardingEvents.OnSignIn(A_HOMESERVER_URL))
+                skipItems(1) // Loading
+
+                // Check an error was returned
+                val submittedState = awaitItem()
+                assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
+
+                // Assert the error is then cleared
+                submittedState.eventSink(OnBoardingEvents.ClearError)
+                val clearedState = awaitItem()
+                assertThat(clearedState.loginMode).isEqualTo(AsyncData.Uninitialized)
+            }
+        }
+    }
+}
+
+private fun createPresenter(
+    params: OnBoardingNode.Params = OnBoardingNode.Params(null, null),
+    buildMeta: BuildMeta = aBuildMeta(),
+    featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
+    rageshakeFeatureAvailability: () -> Boolean = { true },
+    loginHelper: LoginHelper = createLoginHelper(),
+) = OnBoardingPresenter(
+    params = params,
+    buildMeta = buildMeta,
+    featureFlagService = featureFlagService,
+    rageshakeFeatureAvailability = rageshakeFeatureAvailability,
+    loginHelper = loginHelper,
+)
+
+fun createLoginHelper(
+    oidcActionFlow: OidcActionFlow = DefaultOidcActionFlow(),
+    authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
+    defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(),
+    webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
+): LoginHelper = LoginHelper(
+    oidcActionFlow = oidcActionFlow,
+    authenticationService = authenticationService,
+    defaultLoginUserStory = defaultLoginUserStory,
+    webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
+)
diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/onboarding/OnboardingViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt
similarity index 67%
rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/onboarding/OnboardingViewTest.kt
rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt
index 9565eca6144..141b5c29aeb 100644
--- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/onboarding/OnboardingViewTest.kt
+++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt
@@ -5,16 +5,22 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-package io.element.android.features.login.impl.onboarding
+package io.element.android.features.login.impl.screens.onboarding
 
 import androidx.activity.ComponentActivity
 import androidx.compose.ui.test.junit4.AndroidComposeTestRule
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import io.element.android.features.login.impl.R
+import io.element.android.libraries.architecture.AsyncData
+import io.element.android.libraries.matrix.api.auth.OidcDetails
+import io.element.android.libraries.matrix.test.AN_EXCEPTION
 import io.element.android.libraries.ui.strings.CommonStrings
 import io.element.android.tests.testutils.EnsureNeverCalled
+import io.element.android.tests.testutils.EnsureNeverCalledWithParam
+import io.element.android.tests.testutils.EventsRecorder
 import io.element.android.tests.testutils.clickOn
 import io.element.android.tests.testutils.ensureCalledOnce
 import org.junit.Rule
@@ -74,6 +80,34 @@ class OnboardingViewTest {
         }
     }
 
+    @Test
+    fun `when sign in to pre defined account provider - clicking on button emits the expected event`() {
+        val eventSink = EventsRecorder<OnBoardingEvents>()
+        rule.setOnboardingView(
+            state = anOnBoardingState(
+                defaultAccountProvider = "element.io",
+                eventSink = eventSink,
+            ),
+        )
+        val buttonText = rule.activity.getString(R.string.screen_onboarding_sign_in_to, "element.io")
+        rule.onNodeWithText(buttonText).performClick()
+        eventSink.assertSingle(OnBoardingEvents.OnSignIn("element.io"))
+    }
+
+    @Test
+    fun `when error is displayed - closing the dialog emits the expected event`() {
+        val eventSink = EventsRecorder<OnBoardingEvents>()
+        rule.setOnboardingView(
+            state = anOnBoardingState(
+                defaultAccountProvider = "element.io",
+                loginMode = AsyncData.Failure(AN_EXCEPTION),
+                eventSink = eventSink,
+            ),
+        )
+        rule.clickOn(CommonStrings.action_ok)
+        eventSink.assertSingle(OnBoardingEvents.ClearError)
+    }
+
     @Test
     fun `clicking on report a problem calls the sign in callback`() {
         ensureCalledOnce { callback ->
@@ -106,6 +140,10 @@ class OnboardingViewTest {
         onSignIn: () -> Unit = EnsureNeverCalled(),
         onCreateAccount: () -> Unit = EnsureNeverCalled(),
         onReportProblem: () -> Unit = EnsureNeverCalled(),
+        onOidcDetails: (OidcDetails) -> Unit = EnsureNeverCalledWithParam(),
+        onNeedLoginPassword: () -> Unit = EnsureNeverCalled(),
+        onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
+        onCreateAccountContinue: (url: String) -> Unit = EnsureNeverCalledWithParam(),
     ) {
         setContent {
             OnBoardingView(
@@ -114,6 +152,10 @@ class OnboardingViewTest {
                 onSignIn = onSignIn,
                 onCreateAccount = onCreateAccount,
                 onReportProblem = onReportProblem,
+                onOidcDetails = onOidcDetails,
+                onNeedLoginPassword = onNeedLoginPassword,
+                onLearnMoreClick = onLearnMoreClick,
+                onCreateAccountContinue = onCreateAccountContinue,
             )
         }
     }
diff --git a/features/login/test/build.gradle.kts b/features/login/test/build.gradle.kts
new file mode 100644
index 00000000000..d786420def4
--- /dev/null
+++ b/features/login/test/build.gradle.kts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+plugins {
+    id("io.element.android-compose-library")
+}
+
+android {
+    namespace = "io.element.android.features.login.test"
+}
+
+dependencies {
+    implementation(projects.features.login.api)
+    implementation(projects.tests.testutils)
+}
diff --git a/features/login/test/src/main/kotlin/io/element/android/features/login/test/FakeLoginIntentResolver.kt b/features/login/test/src/main/kotlin/io/element/android/features/login/test/FakeLoginIntentResolver.kt
new file mode 100644
index 00000000000..bcdbb816210
--- /dev/null
+++ b/features/login/test/src/main/kotlin/io/element/android/features/login/test/FakeLoginIntentResolver.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+package io.element.android.features.login.test
+
+import io.element.android.features.login.api.LoginIntentResolver
+import io.element.android.features.login.api.LoginParams
+import io.element.android.tests.testutils.lambda.lambdaError
+
+class FakeLoginIntentResolver(
+    private val parseResult: (String) -> LoginParams? = { lambdaError() }
+) : LoginIntentResolver {
+    override fun parse(uriString: String): LoginParams? {
+        return parseResult(uriString)
+    }
+}
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt
index 24732f5b65c..11a02576856 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt
@@ -19,7 +19,6 @@ import io.element.android.libraries.matrix.api.core.TransactionId
 import io.element.android.libraries.matrix.api.core.UniqueId
 import io.element.android.libraries.matrix.api.core.UserId
 import io.element.android.libraries.matrix.api.room.RoomNotificationMode
-import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
 
 const val A_USER_NAME = "alice"
 const val A_USER_NAME_2 = "Bob"
@@ -73,7 +72,6 @@ const val A_HOMESERVER_URL_2 = "matrix-client.org"
 val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = true, supportsOidcLogin = false)
 val A_HOMESERVER_OIDC = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = false, supportsOidcLogin = true)
 val A_ROOM_NOTIFICATION_MODE = RoomNotificationMode.MUTE
-val A_ROOM_NOTIFICATION_SETTINGS = RoomNotificationSettings(mode = A_ROOM_NOTIFICATION_MODE, isDefault = false)
 
 const val AN_AVATAR_URL = "mxc://data"
 
@@ -88,3 +86,5 @@ val A_SERVER_LIST = listOf("server1", "server2")
 
 const val A_TIMESTAMP = 567L
 const val A_FORMATTED_DATE = "April 6, 1980 at 6:35 PM"
+
+const val A_LOGIN_HINT = "mxid:@alice:example.org"
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeMatrixAuthenticationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeMatrixAuthenticationService.kt
index 3de9da37a49..f236c37da81 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeMatrixAuthenticationService.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeMatrixAuthenticationService.kt
@@ -87,7 +87,10 @@ class FakeMatrixAuthenticationService(
         return importCreatedSessionLambda(externalSession)
     }
 
-    override suspend fun getOidcUrl(prompt: OidcPrompt): Result<OidcDetails> = simulateLongTask {
+    override suspend fun getOidcUrl(
+        prompt: OidcPrompt,
+        loginHint: String?,
+    ): Result<OidcDetails> = simulateLongTask {
         oidcError?.let { Result.failure(it) } ?: Result.success(A_OIDC_DATA)
     }
 
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_0_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_0_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_0_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_1_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_1_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_1_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_2_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_2_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_2_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_3_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_3_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_3_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_4_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Day_4_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_4_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_5_en.png
new file mode 100644
index 00000000000..fbacb365c1a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Day_5_en.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e93b2b62089570439aa10baf41d0ded8cdac23ba234a5bb32ba118f41e1f7385
+size 313315
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_0_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_0_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_0_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_1_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_1_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_1_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_2_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_2_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_2_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_3_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_3_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_3_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_4_en.png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/features.login.impl.onboarding_OnBoardingView_Night_4_en.png
rename to tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_4_en.png
diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_5_en.png
new file mode 100644
index 00000000000..b622d48595d
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.onboarding_OnBoardingView_Night_5_en.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aa123acbffde2545f7f76ce58ede9d1fb80668d2d8576bf5f920a49f91c1e474
+size 394540