diff --git a/changelog.d/4382.feature b/changelog.d/4382.feature new file mode 100644 index 0000000000..64230dccb1 --- /dev/null +++ b/changelog.d/4382.feature @@ -0,0 +1 @@ +Updates onboarding splash screen to have a dedicated sign in button and removes the dual purpose sign in/up stage \ No newline at end of file diff --git a/library/ui-styles/src/debug/res/drawable/ic_debug_icon.xml b/library/ui-styles/src/main/res/drawable/ic_debug_icon.xml similarity index 100% rename from library/ui-styles/src/debug/res/drawable/ic_debug_icon.xml rename to library/ui-styles/src/main/res/drawable/ic_debug_icon.xml diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 7b869e8cd2..9db73230ee 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===117 +enum class===118 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/BooleanFeatureItem.kt b/vector/src/debug/java/im/vector/app/features/debug/features/BooleanFeatureItem.kt new file mode 100644 index 0000000000..02c0aa82af --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/features/BooleanFeatureItem.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2019 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.debug.features + +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Spinner +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel + +@EpoxyModelClass(layout = im.vector.app.R.layout.item_feature) +abstract class BooleanFeatureItem : VectorEpoxyModel() { + + @EpoxyAttribute + lateinit var feature: Feature.BooleanFeature + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var listener: Listener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.label.text = feature.label + + holder.optionsSpinner.apply { + val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item) + val options = listOf( + "DEFAULT - ${feature.featureDefault.toEmoji()}", + "✅", + "❌" + ) + arrayAdapter.addAll(options) + adapter = arrayAdapter + + feature.featureOverride?.let { + setSelection(options.indexOf(it.toEmoji()), false) + } + + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + when (position) { + 0 -> listener?.onBooleanOptionSelected(option = null, feature) + else -> listener?.onBooleanOptionSelected(options[position].fromEmoji(), feature) + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // do nothing + } + } + } + } + + class Holder : VectorEpoxyHolder() { + val label by bind(im.vector.app.R.id.feature_label) + val optionsSpinner by bind(im.vector.app.R.id.feature_options) + } + + interface Listener { + fun onBooleanOptionSelected(option: Boolean?, feature: Feature.BooleanFeature) + } +} + +private fun Boolean.toEmoji() = if (this) "✅" else "❌" +private fun String.fromEmoji() = when (this) { + "✅" -> true + "❌" -> false + else -> error("unexpected input $this") +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt index e31f073614..dcd42bedcf 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt @@ -35,10 +35,14 @@ class DebugFeaturesSettingsActivity : VectorBaseActivity> onOptionSelected(option: T?, feature: Feature.EnumFeature) { + controller.listener = object : FeaturesController.Listener { + override fun > onEnumOptionSelected(option: T?, feature: Feature.EnumFeature) { debugFeatures.overrideEnum(option, feature.type) } + + override fun onBooleanOptionSelected(option: Boolean?, feature: Feature.BooleanFeature) { + debugFeatures.override(option, feature.key) + } } views.genericRecyclerView.configureWith(controller) controller.setData(debugFeaturesStateFactory.create()) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 637071dd59..7ae7a53963 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -28,17 +28,26 @@ class DebugFeaturesStateFactory @Inject constructor( return FeaturesState(listOf( createEnumFeature( label = "Onboarding variant", - selection = debugFeatures.onboardingVariant(), - default = defaultFeatures.onboardingVariant() + featureOverride = debugFeatures.onboardingVariant(), + featureDefault = defaultFeatures.onboardingVariant() + ), + + Feature.BooleanFeature( + label = "FTUE Splash - I already have an account", + featureOverride = debugFeatures.isAlreadyHaveAccountSplashEnabled().takeIf { + debugFeatures.hasOverride(DebugFeatureKeys.alreadyHaveAnAccount) + }, + featureDefault = defaultFeatures.isAlreadyHaveAccountSplashEnabled(), + key = DebugFeatureKeys.alreadyHaveAnAccount ) )) } - private inline fun > createEnumFeature(label: String, selection: T, default: T): Feature { + private inline fun > createEnumFeature(label: String, featureOverride: T, featureDefault: T): Feature { return Feature.EnumFeature( label = label, - selection = selection.takeIf { debugFeatures.hasEnumOverride(T::class) }, - default = default, + override = featureOverride.takeIf { debugFeatures.hasEnumOverride(T::class) }, + default = featureDefault, options = enumValues().toList(), type = T::class ) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 4dbb6a5698..daab981956 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -20,6 +20,7 @@ import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore @@ -42,13 +43,26 @@ class DebugVectorFeatures( return readPreferences().getEnum() ?: vectorFeatures.onboardingVariant() } + override fun isAlreadyHaveAccountSplashEnabled(): Boolean = readPreferences()[DebugFeatureKeys.alreadyHaveAnAccount] + ?: vectorFeatures.isAlreadyHaveAccountSplashEnabled() + + fun override(value: T?, key: Preferences.Key) = updatePreferences { + if (value == null) { + it.remove(key) + } else { + it[key] = value + } + } + + fun hasOverride(key: Preferences.Key) = readPreferences().contains(key) + fun > hasEnumOverride(type: KClass) = readPreferences().containsEnum(type) - fun > overrideEnum(value: T?, type: KClass) { + fun > overrideEnum(value: T?, type: KClass) = updatePreferences { if (value == null) { - updatePreferences { it.removeEnum(type) } + it.removeEnum(type) } else { - updatePreferences { it.putEnum(value, type) } + it.putEnum(value, type) } } @@ -76,3 +90,8 @@ private inline fun > Preferences.getEnum(): T? { private inline fun > enumPreferencesKey() = enumPreferencesKey(T::class) private fun > enumPreferencesKey(type: KClass) = stringPreferencesKey("enum-${type.simpleName}") + +object DebugFeatureKeys { + + val alreadyHaveAnAccount = booleanPreferencesKey("already-have-an-account") +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/EnumFeatureItem.kt b/vector/src/debug/java/im/vector/app/features/debug/features/EnumFeatureItem.kt index 5dd2f9efa9..d5b2ec1080 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/EnumFeatureItem.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/EnumFeatureItem.kt @@ -45,14 +45,14 @@ abstract class EnumFeatureItem : VectorEpoxyModel() { arrayAdapter.addAll(feature.options.map { it.name }) adapter = arrayAdapter - feature.selection?.let { + feature.override?.let { setSelection(feature.options.indexOf(it) + 1, false) } onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { when (position) { - 0 -> listener?.onOptionSelected(option = null, feature) + 0 -> listener?.onEnumOptionSelected(option = null, feature) else -> feature.onOptionSelected(position - 1) } } @@ -65,7 +65,7 @@ abstract class EnumFeatureItem : VectorEpoxyModel() { } private fun > Feature.EnumFeature.onOptionSelected(selection: Int) { - listener?.onOptionSelected(options[selection], this) + listener?.onEnumOptionSelected(options[selection], this) } class Holder : VectorEpoxyHolder() { @@ -74,6 +74,6 @@ abstract class EnumFeatureItem : VectorEpoxyModel() { } interface Listener { - fun > onOptionSelected(option: T?, feature: Feature.EnumFeature) + fun > onEnumOptionSelected(option: T?, feature: Feature.EnumFeature) } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/FeaturesController.kt b/vector/src/debug/java/im/vector/app/features/debug/features/FeaturesController.kt index 0a05c76d69..3a685314fd 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/FeaturesController.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/FeaturesController.kt @@ -16,6 +16,7 @@ package im.vector.app.features.debug.features +import androidx.datastore.preferences.core.Preferences import com.airbnb.epoxy.TypedEpoxyController import javax.inject.Inject import kotlin.reflect.KClass @@ -28,16 +29,23 @@ sealed interface Feature { data class EnumFeature>( val label: String, - val selection: T?, + val override: T?, val default: T, val options: List, val type: KClass ) : Feature + + data class BooleanFeature( + val label: String, + val featureOverride: Boolean?, + val featureDefault: Boolean, + val key: Preferences.Key + ) : Feature } class FeaturesController @Inject constructor() : TypedEpoxyController() { - var listener: EnumFeatureItem.Listener? = null + var listener: Listener? = null override fun buildModels(data: FeaturesState?) { if (data == null) return @@ -49,7 +57,14 @@ class FeaturesController @Inject constructor() : TypedEpoxyController booleanFeatureItem { + id(index) + feature(feature) + listener(this@FeaturesController.listener) + } } } } + + interface Listener : EnumFeatureItem.Listener, BooleanFeatureItem.Listener } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 4228c6ebcd..0349b15c4d 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -22,6 +22,8 @@ interface VectorFeatures { fun onboardingVariant(): OnboardingVariant + fun isAlreadyHaveAccountSplashEnabled(): Boolean + enum class OnboardingVariant { LEGACY, LOGIN_2, @@ -36,4 +38,5 @@ interface VectorFeatures { class DefaultVectorFeatures : VectorFeatures { override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT + override fun isAlreadyHaveAccountSplashEnabled() = true } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt index dd0bfc0ed6..2fab175168 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt @@ -57,6 +57,7 @@ class LoginSplashFragment @Inject constructor( views.loginSplashVersion.text = "Version : ${BuildConfig.VERSION_NAME}\n" + "Branch: ${BuildConfig.GIT_BRANCH_NAME}\n" + "Build: ${BuildConfig.BUILD_NUMBER}" + views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) } } } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt index 6cfebf776a..db6e86250b 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt @@ -55,6 +55,7 @@ class LoginSplashSignUpSignInSelectionFragment2 @Inject constructor( views.loginSplashVersion.text = "Version : ${BuildConfig.VERSION_NAME}\n" + "Branch: ${BuildConfig.GIT_BRANCH_NAME}\n" + "Build: ${BuildConfig.BUILD_NUMBER}" + views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index 4af0825044..bb1d3cc52d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -26,7 +26,8 @@ import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.internal.network.ssl.Fingerprint sealed class OnboardingAction : VectorViewModelAction { - data class OnGetStarted(val resetLoginConfig: Boolean) : OnboardingAction() + data class OnGetStarted(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction() + data class OnIAlreadyHaveAnAccount(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction() data class UpdateServerType(val serverType: ServerType) : OnboardingAction() data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt index 0db9fb0c76..f0cf9464a6 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt @@ -34,7 +34,7 @@ import javax.inject.Inject class OnboardingActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity { private val onboardingVariant by lifecycleAwareLazy { - onboardingVariantFactory.create(this, onboardingViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) + onboardingVariantFactory.create(this, views = views, onboardingViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) } @Inject lateinit var onboardingVariantFactory: OnboardingVariantFactory diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt index ea0ada56ba..5f861d8808 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt @@ -16,8 +16,10 @@ package im.vector.app.features.onboarding +import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.features.VectorFeatures import im.vector.app.features.login2.LoginViewModel2 +import im.vector.app.features.onboarding.ftueauth.FtueAuthVariant import javax.inject.Inject class OnboardingVariantFactory @Inject constructor( @@ -25,18 +27,19 @@ class OnboardingVariantFactory @Inject constructor( ) { fun create(activity: OnboardingActivity, + views: ActivityLoginBinding, onboardingViewModel: Lazy, loginViewModel2: Lazy ) = when (vectorFeatures.onboardingVariant()) { - VectorFeatures.OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE") - VectorFeatures.OnboardingVariant.FTUE_AUTH -> OnboardingAuthVariant( - views = activity.getBinding(), + VectorFeatures.OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE") + VectorFeatures.OnboardingVariant.FTUE_AUTH -> FtueAuthVariant( + views = views, onboardingViewModel = onboardingViewModel.value, activity = activity, supportFragmentManager = activity.supportFragmentManager ) - VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant( - views = activity.getBinding(), + VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant( + views = views, loginViewModel = loginViewModel2.value, activity = activity, supportFragmentManager = activity.supportFragmentManager diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 32a6d63f9b..5d1e0fdade 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -121,7 +121,8 @@ class OnboardingViewModel @AssistedInject constructor( override fun handle(action: OnboardingAction) { when (action) { - is OnboardingAction.OnGetStarted -> handleOnGetStarted(action) + is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) + is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.InitWith -> handleInitWith(action) @@ -140,10 +141,11 @@ class OnboardingViewModel @AssistedInject constructor( }.exhaustive } - private fun handleOnGetStarted(action: OnboardingAction.OnGetStarted) { - if (action.resetLoginConfig) { + private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) { + if (resetConfig) { loginConfig = null } + setState { copy(onboardingFlow = onboardingFlow) } val configUrl = loginConfig?.homeServerUrl?.takeIf { it.isNotEmpty() } if (configUrl != null) { @@ -822,7 +824,17 @@ class OnboardingViewModel @AssistedInject constructor( // Notify the UI _viewEvents.post(OnboardingViewEvents.OutdatedHomeserver) } - _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) + + withState { + when (it.onboardingFlow) { + OnboardingFlow.SignIn -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.SignIn)) + OnboardingFlow.SignUp -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.SignUp)) + OnboardingFlow.SignInSignUp, + null -> { + _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) + } + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 7a6537f433..fd25f3901e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -33,6 +33,9 @@ data class OnboardingViewState( val asyncResetMailConfirmed: Async = Uninitialized, val asyncRegistration: Async = Uninitialized, + @PersistState + val onboardingFlow: OnboardingFlow? = null, + // User choices @PersistState val serverType: ServerType = ServerType.Unknown, @@ -74,3 +77,9 @@ data class OnboardingViewState( return asyncLoginAction is Success } } + +enum class OnboardingFlow { + SignIn, + SignUp, + SignInSignUp +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt index 06bba82a2d..f8f1d7919b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt @@ -22,12 +22,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.BuildConfig import im.vector.app.R -import im.vector.app.databinding.FragmentLoginSplashBinding -import im.vector.app.features.login.AbstractLoginFragment -import im.vector.app.features.login.LoginAction +import im.vector.app.databinding.FragmentFtueAuthSplashBinding +import im.vector.app.features.VectorFeatures +import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.failure.Failure import java.net.UnknownHostException @@ -37,21 +39,25 @@ import javax.inject.Inject * In this screen, the user is viewing an introduction to what he can do with this application */ class FtueAuthSplashFragment @Inject constructor( - private val vectorPreferences: VectorPreferences -) : AbstractLoginFragment() { + private val vectorPreferences: VectorPreferences, + private val vectorFeatures: VectorFeatures +) : AbstractFtueAuthFragment() { - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSplashBinding { - return FragmentLoginSplashBinding.inflate(inflater, container, false) + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthSplashBinding { + return FragmentFtueAuthSplashBinding.inflate(inflater, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupViews() } private fun setupViews() { views.loginSplashSubmit.debouncedClicks { getStarted() } + views.loginSplashAlreadyHaveAccount.apply { + isVisible = vectorFeatures.isAlreadyHaveAccountSplashEnabled() + debouncedClicks { alreadyHaveAnAccount() } + } if (BuildConfig.DEBUG || vectorPreferences.developerMode()) { views.loginSplashVersion.isVisible = true @@ -59,11 +65,17 @@ class FtueAuthSplashFragment @Inject constructor( views.loginSplashVersion.text = "Version : ${BuildConfig.VERSION_NAME}\n" + "Branch: ${BuildConfig.GIT_BRANCH_NAME}\n" + "Build: ${BuildConfig.BUILD_NUMBER}" + views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) } } } private fun getStarted() { - loginViewModel.handle(LoginAction.OnGetStarted(resetLoginConfig = false)) + val getStartedFlow = if (vectorFeatures.isAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp + viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow)) + } + + private fun alreadyHaveAnAccount() { + viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(resetLoginConfig = false, onboardingFlow = OnboardingFlow.SignIn)) } override fun resetViewModel() { @@ -74,12 +86,13 @@ class FtueAuthSplashFragment @Inject constructor( if (throwable is Failure.NetworkConnection && throwable.ioException is UnknownHostException) { // Invalid homeserver from URL config - val url = loginViewModel.getInitialHomeServerUrl().orEmpty() + val url = viewModel.getInitialHomeServerUrl().orEmpty() MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.dialog_title_error) .setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url)) .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> - loginViewModel.handle(LoginAction.OnGetStarted(resetLoginConfig = true)) + val flow = withState(viewModel) { it.onboardingFlow } ?: OnboardingFlow.SignInSignUp + viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = true, flow)) } .setNegativeButton(R.string.action_cancel, null) .show() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt similarity index 87% rename from vector/src/main/java/im/vector/app/features/onboarding/OnboardingAuthVariant.kt rename to vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 49ceedc381..f17899dff6 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.onboarding +package im.vector.app.features.onboarding.ftueauth import android.content.Intent import android.view.View @@ -42,21 +42,12 @@ import im.vector.app.features.login.SignMode import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.login.isSupported import im.vector.app.features.login.terms.toLocalizedLoginTerms -import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragmentArgument -import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragmentArgument -import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordMailConfirmationFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordSuccessFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthServerSelectionFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthServerUrlFormFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthSignUpSignInSelectionFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragmentArgument -import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment +import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingActivity +import im.vector.app.features.onboarding.OnboardingVariant +import im.vector.app.features.onboarding.OnboardingViewEvents +import im.vector.app.features.onboarding.OnboardingViewModel +import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragmentArgument import org.matrix.android.sdk.api.auth.registration.FlowResult @@ -66,7 +57,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" -class OnboardingAuthVariant( +class FtueAuthVariant( private val views: ActivityLoginBinding, private val onboardingViewModel: OnboardingViewModel, private val activity: VectorBaseActivity, @@ -120,17 +111,17 @@ class OnboardingAuthVariant( activity.addFragment(views.loginFragmentContainer, FtueAuthSplashFragment::class.java) } - private fun handleOnboardingViewEvents(onboardingViewEvents: OnboardingViewEvents) { - when (onboardingViewEvents) { + private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) { + when (viewEvents) { is OnboardingViewEvents.RegistrationFlowResult -> { // Check that all flows are supported by the application - if (onboardingViewEvents.flowResult.missingStages.any { !it.isSupported() }) { + if (viewEvents.flowResult.missingStages.any { !it.isSupported() }) { // Display a popup to propose use web fallback onRegistrationStageNotSupported() } else { - if (onboardingViewEvents.isRegistrationStarted) { + if (viewEvents.isRegistrationStarted) { // Go on with registration flow - handleRegistrationNavigation(onboardingViewEvents.flowResult) + handleRegistrationNavigation(viewEvents.flowResult) } else { // First ask for login and password // I add a tag to indicate that this fragment is a registration stage. @@ -163,13 +154,13 @@ class OnboardingAuthVariant( // TODO Disabled because it provokes a flickering // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) }) - is OnboardingViewEvents.OnServerSelectionDone -> onServerSelectionDone(onboardingViewEvents) - is OnboardingViewEvents.OnSignModeSelected -> onSignModeSelected(onboardingViewEvents) + is OnboardingViewEvents.OnServerSelectionDone -> onServerSelectionDone(viewEvents) + is OnboardingViewEvents.OnSignModeSelected -> onSignModeSelected(viewEvents) is OnboardingViewEvents.OnLoginFlowRetrieved -> activity.addFragmentToBackstack(views.loginFragmentContainer, FtueAuthSignUpSignInSelectionFragment::class.java, option = commonOption) - is OnboardingViewEvents.OnWebLoginError -> onWebLoginError(onboardingViewEvents) + is OnboardingViewEvents.OnWebLoginError -> onWebLoginError(viewEvents) is OnboardingViewEvents.OnForgetPasswordClicked -> activity.addFragmentToBackstack(views.loginFragmentContainer, FtueAuthResetPasswordFragment::class.java, @@ -195,7 +186,7 @@ class OnboardingAuthVariant( supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, FtueAuthWaitForEmailFragment::class.java, - FtueAuthWaitForEmailFragmentArgument(onboardingViewEvents.email), + FtueAuthWaitForEmailFragmentArgument(viewEvents.email), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) } @@ -204,7 +195,7 @@ class OnboardingAuthVariant( supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, FtueAuthGenericTextInputFormFragment::class.java, - FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, onboardingViewEvents.msisdn), + FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, viewEvents.msisdn), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) } @@ -215,11 +206,11 @@ class OnboardingAuthVariant( }.exhaustive } - private fun updateWithState(onboardingViewState: OnboardingViewState) { - if (onboardingViewState.isUserLogged()) { + private fun updateWithState(viewState: OnboardingViewState) { + if (viewState.isUserLogged()) { val intent = HomeActivity.newIntent( activity, - accountCreation = onboardingViewState.signMode == SignMode.SignUp + accountCreation = viewState.signMode == SignMode.SignUp ) activity.startActivity(intent) activity.finish() @@ -227,7 +218,7 @@ class OnboardingAuthVariant( } // Loading - views.loginLoading.isVisible = onboardingViewState.isLoading() + views.loginLoading.isVisible = viewState.isLoading() } private fun onWebLoginError(onWebLoginError: OnboardingViewEvents.OnWebLoginError) { diff --git a/vector/src/main/res/layout/fragment_ftue_auth_splash.xml b/vector/src/main/res/layout/fragment_ftue_auth_splash.xml new file mode 100644 index 0000000000..39c0ad3007 --- /dev/null +++ b/vector/src/main/res/layout/fragment_ftue_auth_splash.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +