Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ class RootFlowNode(
val transitionHandler = rememberDelegateTransitionHandler<NavTarget, BackStack.State> { navTarget ->
when (navTarget) {
is NavTarget.SplashScreen,
is NavTarget.LoggedInFlow -> backstackFader
is NavTarget.LoggedInFlow,
is NavTarget.NotLoggedInFlow -> backstackFader
else -> backstackSlider
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.pop
import com.bumble.appyx.navmodel.backstack.operation.push
import com.bumble.appyx.navmodel.backstack.operation.replace
import com.bumble.appyx.navmodel.backstack.operation.singleTop
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Assisted
Expand All @@ -30,9 +31,11 @@ import io.element.android.annotations.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.classic.ElementClassicConnection
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.chooseaccountprovider.ChooseAccountProviderNode
import io.element.android.features.login.impl.screens.classic.ClassicFlowNode
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
Expand Down Expand Up @@ -63,9 +66,10 @@ class LoginFlowNode(
private val oidcActionFlow: OidcActionFlow,
@AppCoroutineScope
private val appCoroutineScope: CoroutineScope,
private val elementClassicConnection: ElementClassicConnection,
) : BaseFlowNode<LoginFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.OnBoarding,
initialElement = NavTarget.CheckClassicFlow,
savedStateMap = buildContext.savedStateMap,
),
buildContext = buildContext,
Expand Down Expand Up @@ -103,7 +107,12 @@ class LoginFlowNode(

sealed interface NavTarget : Parcelable {
@Parcelize
data object OnBoarding : NavTarget
data object CheckClassicFlow : NavTarget

@Parcelize
data class OnBoarding(
val showBackButton: Boolean,
) : NavTarget

@Parcelize
data object QrCode : NavTarget
Expand All @@ -123,15 +132,41 @@ class LoginFlowNode(
data object SearchAccountProvider : NavTarget

@Parcelize
data object LoginPassword : NavTarget
data class LoginPassword(
val initialLogin: String = "",
) : NavTarget

@Parcelize
data class CreateAccount(val url: String) : NavTarget
}

override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.OnBoarding -> {
NavTarget.CheckClassicFlow -> {
val callback = object : ClassicFlowNode.Callback {
override fun navigateToOnBoarding(allowBackNavigation: Boolean) {
if (allowBackNavigation) {
backstack.push(NavTarget.OnBoarding(showBackButton = true))
} else {
backstack.replace(NavTarget.OnBoarding(showBackButton = false))
}
}

override fun navigateToLoginPassword() {
backstack.push(NavTarget.LoginPassword())
}

override fun navigateToOidc(oidcDetails: OidcDetails) {
navigateToMas(oidcDetails)
}

override fun navigateToCreateAccount(url: String) {
backstack.push(NavTarget.CreateAccount(url))
}
}
createNode<ClassicFlowNode>(buildContext, listOf(callback))
}
is NavTarget.OnBoarding -> {
val callback = object : OnBoardingNode.Callback {
override fun navigateToSignUpFlow() {
backstack.push(
Expand Down Expand Up @@ -166,17 +201,22 @@ class LoginFlowNode(
}

override fun navigateToLoginPassword() {
backstack.push(NavTarget.LoginPassword)
backstack.push(NavTarget.LoginPassword())
}

override fun onDone() {
callback.onDone()
if (navTarget.showBackButton) {
backstack.pop()
} else {
callback.onDone()
}
}
}
val params = inputs<Params>()
val inputs = OnBoardingNode.Params(
accountProvider = params.accountProvider,
loginHint = params.loginHint,
showBackButton = navTarget.showBackButton,
)
createNode<OnBoardingNode>(buildContext, listOf(callback, inputs))
}
Expand All @@ -191,7 +231,7 @@ class LoginFlowNode(
}

override fun navigateToLoginPassword() {
backstack.push(NavTarget.LoginPassword)
backstack.push(NavTarget.LoginPassword())
}
}
createNode<ChooseAccountProviderNode>(buildContext, listOf(callback))
Expand All @@ -218,7 +258,7 @@ class LoginFlowNode(
}

override fun navigateToLoginPassword() {
backstack.push(NavTarget.LoginPassword)
backstack.push(NavTarget.LoginPassword())
}

override fun navigateToChangeAccountProvider() {
Expand Down Expand Up @@ -257,8 +297,11 @@ class LoginFlowNode(

createNode<SearchAccountProviderNode>(buildContext, plugins = listOf(callback))
}
NavTarget.LoginPassword -> {
createNode<LoginPasswordNode>(buildContext)
is NavTarget.LoginPassword -> {
val inputs = LoginPasswordNode.Inputs(
initialLogin = navTarget.initialLogin,
)
createNode<LoginPasswordNode>(buildContext, plugins = listOf(inputs))
}
is NavTarget.CreateAccount -> {
val inputs = CreateAccountNode.Inputs(
Expand All @@ -280,6 +323,14 @@ class LoginFlowNode(
override fun View(modifier: Modifier) {
activity = requireNotNull(LocalActivity.current)
darkTheme = !ElementTheme.isLightTheme

DisposableEffect(Unit) {
elementClassicConnection.start()
onDispose {
elementClassicConnection.stop()
}
}

DisposableEffect(Unit) {
onDispose {
activity = null
Expand All @@ -288,6 +339,6 @@ class LoginFlowNode(
}
}
}
BackstackView()
BackstackView(transitionHandler = rememberLoginFlowTransitionHandler())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/

package io.element.android.features.login.impl

import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.spring
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.navigation.transition.ModifierTransitionHandler
import com.bumble.appyx.core.navigation.transition.TransitionDescriptor
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.Replace
import com.bumble.appyx.navmodel.backstack.transitionhandler.rememberBackstackFader
import com.bumble.appyx.navmodel.backstack.transitionhandler.rememberBackstackSlider
import io.element.android.libraries.architecture.appyx.rememberDelegateTransitionHandler

/**
* A TransitionHandler that uses fade transition when OnBoarding is replacing the current screen,
* and slide transition for all other cases.
*/
private class LoginFlowTransitionHandler(
private val slider: ModifierTransitionHandler<LoginFlowNode.NavTarget, BackStack.State>,
private val fader: ModifierTransitionHandler<LoginFlowNode.NavTarget, BackStack.State>,
) : ModifierTransitionHandler<LoginFlowNode.NavTarget, BackStack.State>() {
override fun createModifier(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Modifier factory functions should be extensions on Modifier

modifier: Modifier,
transition: Transition<BackStack.State>,
descriptor: TransitionDescriptor<LoginFlowNode.NavTarget, BackStack.State>
): Modifier {
val useFader = descriptor.element is LoginFlowNode.NavTarget.OnBoarding &&
descriptor.operation is Replace
val handler = if (useFader) fader else slider
return handler.createModifier(modifier, transition, descriptor)
}
}

@Composable
fun rememberLoginFlowTransitionHandler(): ModifierTransitionHandler<LoginFlowNode.NavTarget, BackStack.State> {
val slider = rememberBackstackSlider<LoginFlowNode.NavTarget>(
transitionSpec = { spring(stiffness = Spring.StiffnessMediumLow) },
)
val fader = rememberBackstackFader<LoginFlowNode.NavTarget>(
transitionSpec = { spring(stiffness = Spring.StiffnessMediumLow) },
)
return rememberDelegateTransitionHandler {
LoginFlowTransitionHandler(slider, fader)
}
}
Loading
Loading