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 733485fcef..e75ae3b542 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -42,6 +42,7 @@ import io.element.android.appnav.intent.IntentResolver import io.element.android.appnav.intent.ResolvedIntent import io.element.android.appnav.root.RootPresenter import io.element.android.appnav.root.RootView +import io.element.android.features.login.api.LoginUserStory import io.element.android.features.login.api.oidc.OidcAction import io.element.android.features.login.api.oidc.OidcActionFlow import io.element.android.features.preferences.api.CacheService @@ -55,6 +56,7 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first @@ -74,6 +76,7 @@ class RootFlowNode @AssistedInject constructor( private val bugReportEntryPoint: BugReportEntryPoint, private val intentResolver: IntentResolver, private val oidcActionFlow: OidcActionFlow, + private val loginUserStory: LoginUserStory, ) : BackstackNode( backstack = BackStack( @@ -90,8 +93,7 @@ class RootFlowNode @AssistedInject constructor( } private fun observeLoggedInState() { - authenticationService.isLoggedIn() - .distinctUntilChanged() + isUserLoggedInFlow() .combine( cacheService.cacheIndex().onEach { Timber.v("cacheIndex=$it") @@ -114,6 +116,16 @@ class RootFlowNode @AssistedInject constructor( .launchIn(lifecycleScope) } + private fun isUserLoggedInFlow(): Flow { + return combine( + authenticationService.isLoggedIn(), + loginUserStory.loginFlowIsDone() + ) { isLoggedIn, loginFlowIsDone -> + isLoggedIn && loginFlowIsDone + } + .distinctUntilChanged() + } + private fun switchToLoggedInFlow(sessionId: SessionId, cacheIndex: Int) { backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex)) } diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginUserStory.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginUserStory.kt new file mode 100644 index 0000000000..7a6a704d0f --- /dev/null +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginUserStory.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.api + +import kotlinx.coroutines.flow.Flow + +interface LoginUserStory { + fun loginFlowIsDone(): Flow +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginUserStory.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginUserStory.kt new file mode 100644 index 0000000000..aef32f0890 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginUserStory.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.login.api.LoginUserStory +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultLoginUserStory @Inject constructor() : LoginUserStory { + // True by default, will be set to false when the login user story is started, and set to true again once it's done. + private val loginFlowIsDone: MutableStateFlow = MutableStateFlow(true) + + override fun loginFlowIsDone(): Flow = loginFlowIsDone + + fun setLoginFlowIsDone(value: Boolean) { + loginFlowIsDone.value = value + } +} 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 0ed5848240..fe47fb1b67 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 @@ -60,6 +60,7 @@ class LoginFlowNode @AssistedInject constructor( private val customTabAvailabilityChecker: CustomTabAvailabilityChecker, private val customTabHandler: CustomTabHandler, private val accountProviderDataSource: AccountProviderDataSource, + private val defaultLoginUserStory: DefaultLoginUserStory, ) : BackstackNode( backstack = BackStack( initialElement = NavTarget.ConfirmAccountProvider, @@ -77,6 +78,11 @@ class LoginFlowNode @AssistedInject constructor( private val inputs: Inputs = inputs() + override fun onBuilt() { + super.onBuilt() + defaultLoginUserStory.setLoginFlowIsDone(false) + } + sealed interface NavTarget : Parcelable { @Parcelize object ConfirmAccountProvider : NavTarget diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt index 1fc4a10bbb..b2ea5fb985 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter @@ -36,6 +37,7 @@ import javax.inject.Inject class LoginPasswordPresenter @Inject constructor( private val authenticationService: MatrixAuthenticationService, private val accountProviderDataSource: AccountProviderDataSource, + private val defaultLoginUserStory: DefaultLoginUserStory, ) : Presenter { @Composable @@ -77,6 +79,8 @@ class LoginPasswordPresenter @Inject constructor( loggedInState.value = Async.Loading() authenticationService.login(formState.login.trim(), formState.password) .onSuccess { sessionId -> + // We will not navigate to the WaitList screen, so the login user story is done + defaultLoginUserStory.setLoginFlowIsDone(true) loggedInState.value = Async.Success(sessionId) } .onFailure { failure -> diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt index 5604789f55..5ceee99f91 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt @@ -19,4 +19,5 @@ package io.element.android.features.login.impl.screens.waitlistscreen sealed interface WaitListEvents { object AttemptLogin : WaitListEvents object ClearError : WaitListEvents + object Continue : WaitListEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt index 061872839b..9c07204ab2 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt @@ -24,6 +24,7 @@ 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.screens.loginpassword.LoginFormState import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter @@ -38,6 +39,7 @@ class WaitListPresenter @AssistedInject constructor( @Assisted private val formState: LoginFormState, private val buildMeta: BuildMeta, private val authenticationService: MatrixAuthenticationService, + private val defaultLoginUserStory: DefaultLoginUserStory, ) : Presenter { @AssistedFactory @@ -68,6 +70,7 @@ class WaitListPresenter @AssistedInject constructor( } } WaitListEvents.ClearError -> loginAction.value = Async.Uninitialized + WaitListEvents.Continue -> defaultLoginUserStory.setLoginFlowIsDone(true) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt index 79e9f4d613..15105a8048 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt @@ -208,7 +208,7 @@ private fun WaitListContent( } if (state.loginAction is Async.Success) { Button( - onClick = { TODO() }, + onClick = { state.eventSink.invoke(WaitListEvents.Continue) }, colors = ButtonDefaults.buttonColors( containerColor = Color.White, contentColor = Color.Black,