Skip to content

Commit

Permalink
Merge pull request #4927 from vector-im/feature/adm/ftue-usecase
Browse files Browse the repository at this point in the history
FTUE Use case UI/UX
  • Loading branch information
ouchadam authored Jan 13, 2022
2 parents 1c948c1 + c3ac60f commit a208b48
Show file tree
Hide file tree
Showing 21 changed files with 448 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@ class DebugFeaturesStateFactory @Inject constructor(
),
createBooleanFeature(
label = "FTUE Splash - I already have an account",
factory = VectorFeatures::isAlreadyHaveAccountSplashEnabled,
key = DebugFeatureKeys.alreadyHaveAnAccount
key = DebugFeatureKeys.onboardingAlreadyHaveAnAccount,
factory = VectorFeatures::isOnboardingAlreadyHaveAccountSplashEnabled
),
createBooleanFeature(
label = "FTUE Splash - Carousel",
factory = VectorFeatures::isSplashCarouselEnabled,
key = DebugFeatureKeys.splashCarousel
label = "FTUE Splash - carousel",
key = DebugFeatureKeys.onboardingSplashCarousel,
factory = VectorFeatures::isOnboardingSplashCarouselEnabled
),
createBooleanFeature(
label = "FTUE Use Case",
key = DebugFeatureKeys.onboardingUseCase,
factory = VectorFeatures::isOnboardingUseCaseEnabled
)
))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ class DebugVectorFeatures(
return readPreferences().getEnum<VectorFeatures.OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
}

override fun isAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.alreadyHaveAnAccount)
?: vectorFeatures.isAlreadyHaveAccountSplashEnabled()
override fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.onboardingAlreadyHaveAnAccount)
?: vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()

override fun isSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.splashCarousel) ?: vectorFeatures.isSplashCarouselEnabled()
override fun isOnboardingSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.onboardingSplashCarousel)
?: vectorFeatures.isOnboardingSplashCarouselEnabled()

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

fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
if (value == null) {
Expand Down Expand Up @@ -96,6 +99,7 @@ private inline fun <reified T : Enum<T>> enumPreferencesKey() = enumPreferencesK
private fun <T : Enum<T>> enumPreferencesKey(type: KClass<T>) = stringPreferencesKey("enum-${type.simpleName}")

object DebugFeatureKeys {
val alreadyHaveAnAccount = booleanPreferencesKey("already-have-an-account")
val splashCarousel = booleanPreferencesKey("splash-carousel")
val onboardingAlreadyHaveAnAccount = booleanPreferencesKey("onboarding-already-have-an-account")
val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel")
val onboardingUseCase = booleanPreferencesKey("onbboarding-splash-carousel")
}
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 @@ -104,6 +104,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthServerSelectionFragmen
import im.vector.app.features.onboarding.ftueauth.FtueAuthSignUpSignInSelectionFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashCarouselFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthUseCaseFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
Expand Down Expand Up @@ -449,6 +450,11 @@ interface FragmentModule {
@FragmentKey(FtueAuthSplashCarouselFragment::class)
fun bindFtueAuthSplashCarouselFragment(fragment: FtueAuthSplashCarouselFragment): Fragment

@Binds
@IntoMap
@FragmentKey(FtueAuthUseCaseFragment::class)
fun bindFtueAuthUseCaseFragment(fragment: FtueAuthUseCaseFragment): Fragment

@Binds
@IntoMap
@FragmentKey(FtueAuthWaitForEmailFragment::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fun TextView.setTextWithColoredPart(@StringRes fullTextRes: Int,
fun TextView.setTextWithColoredPart(fullText: String,
coloredPart: String,
@AttrRes colorAttribute: Int = R.attr.colorPrimary,
underline: Boolean = false,
underline: Boolean = true,
onClick: (() -> Unit)? = null) {
val color = ThemeUtils.getColor(context, colorAttribute)

Expand All @@ -101,7 +101,6 @@ fun TextView.setTextWithColoredPart(fullText: String,

override fun updateDrawState(ds: TextPaint) {
ds.color = color
ds.isUnderlineText = !underline
}
}
setSpan(clickableSpan, index, index + coloredPart.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
Expand Down
12 changes: 6 additions & 6 deletions vector/src/main/java/im/vector/app/features/VectorFeatures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ import im.vector.app.BuildConfig
interface VectorFeatures {

fun onboardingVariant(): OnboardingVariant

fun isAlreadyHaveAccountSplashEnabled(): Boolean

fun isSplashCarouselEnabled(): Boolean
fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean
fun isOnboardingSplashCarouselEnabled(): Boolean
fun isOnboardingUseCaseEnabled(): Boolean

enum class OnboardingVariant {
LEGACY,
Expand All @@ -35,6 +34,7 @@ interface VectorFeatures {

class DefaultVectorFeatures : VectorFeatures {
override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT
override fun isAlreadyHaveAccountSplashEnabled() = true
override fun isSplashCarouselEnabled() = false
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
override fun isOnboardingSplashCarouselEnabled() = false
override fun isOnboardingUseCaseEnabled() = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2022 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

enum class FtueUseCase {
FRIENDS_FAMILY,
TEAMS,
COMMUNITIES,
SKIP
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ sealed class OnboardingAction : VectorViewModelAction {

data class UpdateServerType(val serverType: ServerType) : OnboardingAction()
data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction()
data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction()
object ResetUseCase : OnboardingAction()
data class UpdateSignMode(val signMode: SignMode) : OnboardingAction()
data class LoginWithToken(val loginToken: String) : OnboardingAction()
data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ sealed class OnboardingViewEvents : VectorViewEvents {

// Navigation event

object OpenUseCaseSelection : OnboardingViewEvents()
object OpenServerSelection : OnboardingViewEvents()
data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents()
object OnLoginFlowRetrieved : OnboardingViewEvents()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.features.VectorFeatures
import im.vector.app.features.login.HomeServerConnectionConfigFactory
import im.vector.app.features.login.LoginConfig
import im.vector.app.features.login.LoginMode
Expand Down Expand Up @@ -71,7 +72,8 @@ class OnboardingViewModel @AssistedInject constructor(
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
private val reAuthHelper: ReAuthHelper,
private val stringProvider: StringProvider,
private val homeServerHistoryService: HomeServerHistoryService
private val homeServerHistoryService: HomeServerHistoryService,
private val vectorFeatures: VectorFeatures
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {

@AssistedFactory
Expand Down Expand Up @@ -123,6 +125,8 @@ class OnboardingViewModel @AssistedInject constructor(
when (action) {
is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
is OnboardingAction.UpdateUseCase -> handleUpdateUseCase()
OnboardingAction.ResetUseCase -> resetUseCase()
is OnboardingAction.UpdateServerType -> handleUpdateServerType(action)
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
is OnboardingAction.InitWith -> handleInitWith(action)
Expand Down Expand Up @@ -154,15 +158,28 @@ class OnboardingViewModel @AssistedInject constructor(
if (homeServerConnectionConfig == null) {
// Url is invalid, in this case, just use the regular flow
Timber.w("Url from config url was invalid: $configUrl")
_viewEvents.post(OnboardingViewEvents.OpenServerSelection)
continueToPageAfterSplash(onboardingFlow)
} else {
getLoginFlow(homeServerConnectionConfig, ServerType.Other)
}
} else {
_viewEvents.post(OnboardingViewEvents.OpenServerSelection)
continueToPageAfterSplash(onboardingFlow)
}
}

private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) {
val nextOnboardingStep = when (onboardingFlow) {
OnboardingFlow.SignUp -> if (vectorFeatures.isOnboardingUseCaseEnabled()) {
OnboardingViewEvents.OpenUseCaseSelection
} else {
OnboardingViewEvents.OpenServerSelection
}
OnboardingFlow.SignIn,
OnboardingFlow.SignInSignUp -> OnboardingViewEvents.OpenServerSelection
}
_viewEvents.post(nextOnboardingStep)
}

private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) {
// It happens when we get the login flow, or during direct authentication.
// So alter the homeserver config and retrieve again the login flow
Expand Down Expand Up @@ -441,6 +458,15 @@ class OnboardingViewModel @AssistedInject constructor(
}
}

private fun handleUpdateUseCase() {
// TODO act on the use case selection
_viewEvents.post(OnboardingViewEvents.OpenServerSelection)
}

private fun resetUseCase() {
// TODO remove stored use case
}

private fun handleUpdateServerType(action: OnboardingAction.UpdateServerType) {
setState {
copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class FtueAuthSplashCarouselFragment @Inject constructor(

views.loginSplashSubmit.debouncedClicks { getStarted() }
views.loginSplashAlreadyHaveAccount.apply {
isVisible = vectorFeatures.isAlreadyHaveAccountSplashEnabled()
isVisible = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()
debouncedClicks { alreadyHaveAnAccount() }
}

Expand Down Expand Up @@ -111,7 +111,7 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
}

private fun getStarted() {
val getStartedFlow = if (vectorFeatures.isAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
val getStartedFlow = if (vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class FtueAuthSplashFragment @Inject constructor(
private fun setupViews() {
views.loginSplashSubmit.debouncedClicks { getStarted() }
views.loginSplashAlreadyHaveAccount.apply {
isVisible = vectorFeatures.isAlreadyHaveAccountSplashEnabled()
isVisible = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()
debouncedClicks { alreadyHaveAnAccount() }
}

Expand All @@ -70,7 +70,7 @@ class FtueAuthSplashFragment @Inject constructor(
}

private fun getStarted() {
val getStartedFlow = if (vectorFeatures.isAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
val getStartedFlow = if (vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 android.widget.TextView
import androidx.annotation.StringRes
import im.vector.app.R
import im.vector.app.core.extensions.setTextWithColoredPart
import im.vector.app.databinding.FragmentFtueAuthUseCaseBinding
import im.vector.app.features.login.ServerType
import im.vector.app.features.onboarding.FtueUseCase
import im.vector.app.features.onboarding.OnboardingAction
import javax.inject.Inject

class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment<FragmentFtueAuthUseCaseBinding>() {

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

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

private fun setupViews() {
views.useCaseOptionOne.setUseCase(R.string.ftue_auth_use_case_option_one, FtueUseCase.FRIENDS_FAMILY)
views.useCaseOptionTwo.setUseCase(R.string.ftue_auth_use_case_option_two, FtueUseCase.TEAMS)
views.useCaseOptionThree.setUseCase(R.string.ftue_auth_use_case_option_three, FtueUseCase.COMMUNITIES)

views.useCaseSkip.setTextWithColoredPart(
fullTextRes = R.string.ftue_auth_use_case_skip,
coloredTextRes = R.string.ftue_auth_use_case_skip_partial,
underline = false,
colorAttribute = R.attr.colorAccent,
onClick = { viewModel.handle(OnboardingAction.UpdateUseCase(FtueUseCase.SKIP)) }
)

views.useCaseConnectToServer.setOnClickListener {
viewModel.handle(OnboardingAction.UpdateServerType(ServerType.Other))
}
}

override fun resetViewModel() {
viewModel.handle(OnboardingAction.ResetUseCase)
}

private fun TextView.setUseCase(@StringRes label: Int, useCase: FtueUseCase) {
setText(label)
debouncedClicks {
viewModel.handle(OnboardingAction.UpdateUseCase(useCase))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class FtueAuthVariant(
}

private fun addFirstFragment() {
val splashFragment = when (vectorFeatures.isSplashCarouselEnabled()) {
val splashFragment = when (vectorFeatures.isOnboardingSplashCarouselEnabled()) {
true -> FtueAuthSplashCarouselFragment::class.java
else -> FtueAuthSplashFragment::class.java
}
Expand Down Expand Up @@ -155,13 +155,16 @@ class FtueAuthVariant(
activity.addFragmentToBackstack(views.loginFragmentContainer,
FtueAuthServerSelectionFragment::class.java,
option = { ft ->
activity.findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// Disable transition of text
// findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// No transition here now actually
// findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// TODO Disabled because it provokes a flickering
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
if (vectorFeatures.isOnboardingUseCaseEnabled()) {
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
} else {
activity.findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// TODO Disabled because it provokes a flickering
// Disable transition of text
// findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// No transition here now actually
// findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
}
})
is OnboardingViewEvents.OnServerSelectionDone -> onServerSelectionDone(viewEvents)
is OnboardingViewEvents.OnSignModeSelected -> onSignModeSelected(viewEvents)
Expand Down Expand Up @@ -212,6 +215,11 @@ class FtueAuthVariant(
is OnboardingViewEvents.Loading ->
// This is handled by the Fragments
Unit
OnboardingViewEvents.OpenUseCaseSelection -> {
activity.addFragmentToBackstack(views.loginFragmentContainer,
FtueAuthUseCaseFragment::class.java,
option = commonOption)
}
}.exhaustive
}

Expand Down
Loading

0 comments on commit a208b48

Please sign in to comment.