Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FTUE - Account created screen #5158

Merged
merged 13 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions changelog.d/5158.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Starts the FTUE account personalisation flow by adding an account created screen behind a feature flag
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class DebugFeaturesStateFactory @Inject constructor(
label = "FTUE Use Case",
key = DebugFeatureKeys.onboardingUseCase,
factory = VectorFeatures::isOnboardingUseCaseEnabled
),
createBooleanFeature(
label = "FTUE Personalize profile",
key = DebugFeatureKeys.onboardingPersonalize,
factory = VectorFeatures::isOnboardingPersonalizeEnabled
)
))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class DebugVectorFeatures(

override fun isOnboardingUseCaseEnabled(): Boolean = read(DebugFeatureKeys.onboardingUseCase) ?: vectorFeatures.isOnboardingUseCaseEnabled()

override fun isOnboardingPersonalizeEnabled(): Boolean = read(DebugFeatureKeys.onboardingPersonalize)
?: vectorFeatures.isOnboardingPersonalizeEnabled()

fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
if (value == null) {
it.remove(key)
Expand Down Expand Up @@ -102,4 +105,5 @@ object DebugFeatureKeys {
val onboardingAlreadyHaveAnAccount = booleanPreferencesKey("onboarding-already-have-an-account")
val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel")
val onboardingUseCase = booleanPreferencesKey("onbboarding-splash-carousel")
val onboardingPersonalize = booleanPreferencesKey("onbboarding-personalize")
}
6 changes: 6 additions & 0 deletions vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import im.vector.app.features.login2.created.AccountCreatedFragment
import im.vector.app.features.login2.terms.LoginTermsFragment2
import im.vector.app.features.matrixto.MatrixToRoomSpaceFragment
import im.vector.app.features.matrixto.MatrixToUserFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
Expand Down Expand Up @@ -473,6 +474,11 @@ interface FragmentModule {
@FragmentKey(FtueAuthTermsFragment::class)
fun bindFtueAuthTermsFragment(fragment: FtueAuthTermsFragment): Fragment

@Binds
@IntoMap
@FragmentKey(FtueAuthAccountCreatedFragment::class)
fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment

@Binds
@IntoMap
@FragmentKey(UserListFragment::class)
Expand Down
2 changes: 2 additions & 0 deletions vector/src/main/java/im/vector/app/features/VectorFeatures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface VectorFeatures {
fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean
fun isOnboardingSplashCarouselEnabled(): Boolean
fun isOnboardingUseCaseEnabled(): Boolean
fun isOnboardingPersonalizeEnabled(): Boolean

enum class OnboardingVariant {
LEGACY,
Expand All @@ -37,4 +38,5 @@ class DefaultVectorFeatures : VectorFeatures {
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
override fun isOnboardingSplashCarouselEnabled() = true
override fun isOnboardingUseCaseEnabled() = true
override fun isOnboardingPersonalizeEnabled() = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,7 @@ sealed class OnboardingAction : VectorViewModelAction {
data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction()

data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction()

object TakeMeHome : OnboardingAction()
object PersonalizeProfile : OnboardingAction()
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ sealed class OnboardingViewEvents : VectorViewEvents {
data class OnSendMsisdnSuccess(val msisdn: String) : OnboardingViewEvents()

data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : OnboardingViewEvents()
object OnAccountCreated : OnboardingViewEvents()
object OnAccountSignedIn : OnboardingViewEvents()
object OnTakeMeHome : OnboardingViewEvents()
object OnPersonalizeProfile : OnboardingViewEvents()
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ class OnboardingViewModel @AssistedInject constructor(
is OnboardingAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
OnboardingAction.ClearHomeServerHistory -> handleClearHomeServerHistory()
is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
OnboardingAction.PersonalizeProfile -> _viewEvents.post(OnboardingViewEvents.OnPersonalizeProfile)
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need that? It's ok but feels a bit useless when there is really no other code involved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah I wasn't sure about this as well, sounds like I should use OnboardingAction.PostViewEvent which acts as a proxy to post events directly to the ViewModel.viewEvents observer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Thanks!

OnboardingAction.TakeMeHome -> _viewEvents.post(OnboardingViewEvents.OnTakeMeHome)
}.exhaustive
}

Expand Down Expand Up @@ -244,7 +246,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
null
}
?.let { onSessionCreated(it) }
?.let { onSessionCreated(it, isAccountCreated = false) }
}
}
}
Expand Down Expand Up @@ -314,7 +316,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
?.let { data ->
when (data) {
is RegistrationResult.Success -> onSessionCreated(data.session)
is RegistrationResult.Success -> onSessionCreated(data.session, isAccountCreated = true)
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
}
}
Expand Down Expand Up @@ -615,11 +617,11 @@ class OnboardingViewModel @AssistedInject constructor(
}
when (data) {
is WellknownResult.Prompt ->
onWellknownSuccess(action, data, homeServerConnectionConfig)
directLoginOnWellknownSuccess(action, data, homeServerConnectionConfig)
is WellknownResult.FailPrompt ->
// Relax on IS discovery if homeserver is valid
if (data.homeServerUrl != null && data.wellKnown != null) {
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
directLoginOnWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
} else {
onWellKnownError()
}
Expand All @@ -639,9 +641,9 @@ class OnboardingViewModel @AssistedInject constructor(
_viewEvents.post(OnboardingViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))))
}

private suspend fun onWellknownSuccess(action: OnboardingAction.LoginOrRegister,
wellKnownPrompt: WellknownResult.Prompt,
homeServerConnectionConfig: HomeServerConnectionConfig?) {
private suspend fun directLoginOnWellknownSuccess(action: OnboardingAction.LoginOrRegister,
wellKnownPrompt: WellknownResult.Prompt,
homeServerConnectionConfig: HomeServerConnectionConfig?) {
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
?.copy(
homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl),
Expand All @@ -663,7 +665,7 @@ class OnboardingViewModel @AssistedInject constructor(
onDirectLoginError(failure)
return
}
onSessionCreated(data)
onSessionCreated(data, isAccountCreated = true)
}

private fun onDirectLoginError(failure: Throwable) {
Expand Down Expand Up @@ -721,7 +723,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
?.let {
reAuthHelper.data = action.password
onSessionCreated(it)
onSessionCreated(it, isAccountCreated = false)
}
}
}
Expand Down Expand Up @@ -751,8 +753,9 @@ class OnboardingViewModel @AssistedInject constructor(
}
}

private suspend fun onSessionCreated(session: Session) {
awaitState().useCase?.let { useCase ->
private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) {
val state = awaitState()
state.useCase?.let { useCase ->
session.vectorStore(applicationContext).setUseCase(useCase)
}
activeSessionHolder.setActiveSession(session)
Expand All @@ -764,6 +767,11 @@ class OnboardingViewModel @AssistedInject constructor(
asyncLoginAction = Success(Unit)
)
}

when (isAccountCreated) {
true -> _viewEvents.post(OnboardingViewEvents.OnAccountCreated)
false -> _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
}
}

private fun handleWebLoginSuccess(action: OnboardingAction.WebLoginSuccess) = withState { state ->
Expand All @@ -782,7 +790,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
null
}
?.let { onSessionCreated(it) }
?.let { onSessionCreated(it, isAccountCreated = false) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,10 @@ data class OnboardingViewState(
asyncHomeServerLoginFlowRequest is Loading ||
asyncResetPassword is Loading ||
asyncResetMailConfirmed is Loading ||
asyncRegistration is Loading ||
// Keep loading when it is success because of the delay to switch to the next Activity
asyncLoginAction is Success
asyncRegistration is Loading
}

fun isUserLogged(): Boolean {
fun isAuthTaskCompleted(): Boolean {
return asyncLoginAction is Success
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2021 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 im.vector.app.features.onboarding.ftueauth

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.databinding.FragmentFtueAccountCreatedBinding
import im.vector.app.features.onboarding.OnboardingAction
import javax.inject.Inject

class FtueAuthAccountCreatedFragment @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder
) : AbstractFtueAuthFragment<FragmentFtueAccountCreatedBinding>() {

override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAccountCreatedBinding {
return FragmentFtueAccountCreatedBinding.inflate(inflater, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupViews()
}

private fun setupViews() {
views.accountCreatedSubtitle.text = getString(R.string.ftue_account_created_subtitle, activeSessionHolder.getActiveSession().myUserId)
views.accountCreatedPersonalize.setOnClickListener { viewModel.handle(OnboardingAction.PersonalizeProfile) }
views.accountCreatedTakeMeHome.setOnClickListener { viewModel.handle(OnboardingAction.TakeMeHome) }
}

override fun resetViewModel() {
// Nothing to do
}

override fun onBackPressed(toolbarButton: Boolean): Boolean {
viewModel.handle(OnboardingAction.TakeMeHome)
return false
Copy link
Member

Choose a reason for hiding this comment

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

I think you should return true here, but the contract is not clear (too me). So I am not sure. But it looks like you consume the back event so you should stop the propagation (if I am right).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah great catch! 4975406

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.ScreenOrientationLocker
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLoginBinding
Expand Down Expand Up @@ -220,22 +221,20 @@ class FtueAuthVariant(
FtueAuthUseCaseFragment::class.java,
option = commonOption)
}
OnboardingViewEvents.OnAccountCreated -> onAccountCreated()
OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn()
OnboardingViewEvents.OnPersonalizeProfile -> TODO()
OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true)
}.exhaustive
}

private fun updateWithState(viewState: OnboardingViewState) {
if (viewState.isUserLogged()) {
val intent = HomeActivity.newIntent(
activity,
accountCreation = viewState.signMode == SignMode.SignUp
)
activity.startActivity(intent)
activity.finish()
return
views.loginLoading.isVisible = if (vectorFeatures.isOnboardingPersonalizeEnabled()) {
viewState.isLoading()
} else {
// Keep loading when during success because of the delay when switching to the next Activity
viewState.isLoading() || viewState.isAuthTaskCompleted()
}

// Loading
views.loginLoading.isVisible = viewState.isLoading()
}

private fun onWebLoginError(onWebLoginError: OnboardingViewEvents.OnWebLoginError) {
Expand Down Expand Up @@ -368,4 +367,26 @@ class FtueAuthVariant(
else -> Unit // Should not happen
}
}

private fun onAccountSignedIn() {
navigateToHome(createdAccount = false)
}

private fun onAccountCreated() {
if (vectorFeatures.isOnboardingPersonalizeEnabled()) {
activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
activity.replaceFragment(
views.loginFragmentContainer,
FtueAuthAccountCreatedFragment::class.java,
)
} else {
navigateToHome(createdAccount = true)
}
}

private fun navigateToHome(createdAccount: Boolean) {
val intent = HomeActivity.newIntent(activity, accountCreation = createdAccount)
activity.startActivity(intent)
activity.finish()
}
}
Loading