diff --git a/changelog.d/5375.wip b/changelog.d/5375.wip new file mode 100644 index 00000000000..352b2385a92 --- /dev/null +++ b/changelog.d/5375.wip @@ -0,0 +1 @@ +Dynamically showing/hiding onboarding personalisation screens based on the users homeserver capabilities \ No newline at end of file diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt index 4394f5436eb..5e16182f3cd 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt @@ -22,13 +22,17 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore +import im.vector.app.features.HomeserverCapabilitiesOverride import im.vector.app.features.VectorOverrides +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.extensions.orFalse private val Context.dataStore: DataStore by preferencesDataStore(name = "vector_overrides") private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_display") private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback") +private val forceCanChangeDisplayName = booleanPreferencesKey("force_can_change_display_name") +private val forceCanChangeAvatar = booleanPreferencesKey("force_can_change_avatar") class DebugVectorOverrides(private val context: Context) : VectorOverrides { @@ -40,6 +44,13 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { preferences[keyForceLoginFallback].orFalse() } + override val forceHomeserverCapabilities = context.dataStore.data.map { preferences -> + HomeserverCapabilitiesOverride( + canChangeDisplayName = preferences[forceCanChangeDisplayName], + canChangeAvatar = preferences[forceCanChangeAvatar] + ) + } + suspend fun setForceDialPadDisplay(force: Boolean) { context.dataStore.edit { settings -> settings[keyForceDialPadDisplay] = force @@ -51,4 +62,18 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { settings[keyForceLoginFallback] = force } } + + suspend fun setHomeserverCapabilities(block: HomeserverCapabilitiesOverride.() -> HomeserverCapabilitiesOverride) { + val capabilitiesOverride = block(forceHomeserverCapabilities.firstOrNull() ?: HomeserverCapabilitiesOverride(null, null)) + context.dataStore.edit { settings -> + when (capabilitiesOverride.canChangeDisplayName) { + null -> settings.remove(forceCanChangeDisplayName) + else -> settings[forceCanChangeDisplayName] = capabilitiesOverride.canChangeDisplayName + } + when (capabilitiesOverride.canChangeAvatar) { + null -> settings.remove(forceCanChangeAvatar) + else -> settings[forceCanChangeAvatar] = capabilitiesOverride.canChangeAvatar + } + } + } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt index b54d7769011..38253fe7c25 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt @@ -50,6 +50,12 @@ class DebugPrivateSettingsFragment : VectorBaseFragment + viewModel.handle(DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride(option)) + } + views.forceChangeAvatarCapability.bind(it.homeserverCapabilityOverrides.avatar) { option -> + viewModel.handle(DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride(option)) + } views.forceLoginFallback.isChecked = it.forceLoginFallback } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt index 1c76cf6fb26..5dea3dce640 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt @@ -18,7 +18,9 @@ package im.vector.app.features.debug.settings import im.vector.app.core.platform.VectorViewModelAction -sealed class DebugPrivateSettingsViewActions : VectorViewModelAction { - data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions() - data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions() +sealed interface DebugPrivateSettingsViewActions : VectorViewModelAction { + data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions + data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions + data class SetDisplayNameCapabilityOverride(val option: BooleanHomeserverCapabilitiesOverride?) : DebugPrivateSettingsViewActions + data class SetAvatarCapabilityOverride(val option: BooleanHomeserverCapabilitiesOverride?) : DebugPrivateSettingsViewActions } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt index 8d040d4773e..62871023bc6 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt @@ -22,9 +22,12 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.debug.features.DebugVectorOverrides +import im.vector.app.features.debug.settings.DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride +import im.vector.app.features.debug.settings.DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride import kotlinx.coroutines.launch class DebugPrivateSettingsViewModel @AssistedInject constructor( @@ -40,10 +43,10 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { - observeVectorDataStore() + observeVectorOverrides() } - private fun observeVectorDataStore() { + private fun observeVectorOverrides() { debugVectorOverrides.forceDialPad.setOnEach { copy( dialPadVisible = it @@ -52,13 +55,23 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.forceLoginFallback.setOnEach { copy(forceLoginFallback = it) } + debugVectorOverrides.forceHomeserverCapabilities.setOnEach { + val activeDisplayNameOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeDisplayName) + val activeAvatarOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeAvatar) + copy(homeserverCapabilityOverrides = homeserverCapabilityOverrides.copy( + displayName = homeserverCapabilityOverrides.displayName.copy(activeOption = activeDisplayNameOption), + avatar = homeserverCapabilityOverrides.avatar.copy(activeOption = activeAvatarOption), + )) + } } override fun handle(action: DebugPrivateSettingsViewActions) { when (action) { is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action) is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action) - } + is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) + is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) + }.exhaustive } private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) { @@ -72,4 +85,18 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.setForceLoginFallback(action.force) } } + + private fun handSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) { + viewModelScope.launch { + val forceDisplayName = action.option.toBoolean() + debugVectorOverrides.setHomeserverCapabilities { copy(canChangeDisplayName = forceDisplayName) } + } + } + + private fun handSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) { + viewModelScope.launch { + val forceAvatar = action.option.toBoolean() + debugVectorOverrides.setHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) } + } + } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt index 7fca29af8c0..749b11a744d 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt @@ -17,8 +17,23 @@ package im.vector.app.features.debug.settings import com.airbnb.mvrx.MavericksState +import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdown data class DebugPrivateSettingsViewState( val dialPadVisible: Boolean = false, val forceLoginFallback: Boolean = false, + val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides() ) : MavericksState + +data class HomeserverCapabilityOverrides( + val displayName: OverrideDropdown = OverrideDropdown( + label = "Override display name capability", + activeOption = null, + options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled) + ), + val avatar: OverrideDropdown = OverrideDropdown( + label = "Override avatar capability", + activeOption = null, + options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled) + ) +) diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt new file mode 100644 index 00000000000..48ec44f9092 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt @@ -0,0 +1,86 @@ +/* + * 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.debug.settings + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.LinearLayout +import im.vector.app.R +import im.vector.app.databinding.ViewBooleanDropdownBinding + +class OverrideDropdownView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : LinearLayout(context, attrs) { + + private val binding = ViewBooleanDropdownBinding.inflate( + LayoutInflater.from(context), + this + ) + + init { + orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + } + + fun bind(feature: OverrideDropdown, listener: Listener) { + binding.overrideLabel.text = feature.label + + binding.overrideOptions.apply { + val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item) + val options = listOf("Inactive") + feature.options.map { it.label } + arrayAdapter.addAll(options) + adapter = arrayAdapter + + feature.activeOption?.let { + setSelection(options.indexOf(it.label), false) + } + + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + when (position) { + 0 -> listener.onOverrideSelected(option = null) + else -> listener.onOverrideSelected(feature.options[position - 1]) + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // do nothing + } + } + } + } + + fun interface Listener { + fun onOverrideSelected(option: T?) + } + + data class OverrideDropdown( + val label: String, + val options: List, + val activeOption: T?, + ) +} + +interface OverrideOption { + val label: String +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt new file mode 100644 index 00000000000..316e8fb9017 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt @@ -0,0 +1,42 @@ +/* + * 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.debug.settings + +sealed interface BooleanHomeserverCapabilitiesOverride : OverrideOption { + + companion object { + fun from(value: Boolean?) = when (value) { + null -> null + true -> ForceEnabled + false -> ForceDisabled + } + } + + object ForceEnabled : BooleanHomeserverCapabilitiesOverride { + override val label = "Force enabled" + } + + object ForceDisabled : BooleanHomeserverCapabilitiesOverride { + override val label = "Force disabled" + } +} + +fun BooleanHomeserverCapabilitiesOverride?.toBoolean() = when (this) { + null -> null + BooleanHomeserverCapabilitiesOverride.ForceDisabled -> false + BooleanHomeserverCapabilitiesOverride.ForceEnabled -> true +} diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml index 6760c681696..c42ad68dceb 100644 --- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml +++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml @@ -31,6 +31,24 @@ android:layout_height="wrap_content" android:text="Force login and registration fallback" /> + + + + diff --git a/vector/src/debug/res/layout/view_boolean_dropdown.xml b/vector/src/debug/res/layout/view_boolean_dropdown.xml new file mode 100644 index 00000000000..5018d610478 --- /dev/null +++ b/vector/src/debug/res/layout/view_boolean_dropdown.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt index 4128fdbe3c1..daa0d9e0bd6 100644 --- a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt +++ b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt @@ -22,9 +22,16 @@ import kotlinx.coroutines.flow.flowOf interface VectorOverrides { val forceDialPad: Flow val forceLoginFallback: Flow + val forceHomeserverCapabilities: Flow? } +data class HomeserverCapabilitiesOverride( + val canChangeDisplayName: Boolean?, + val canChangeAvatar: Boolean? +) + class DefaultVectorOverrides : VectorOverrides { override val forceDialPad = flowOf(false) override val forceLoginFallback = flowOf(false) + override val forceHomeserverCapabilities: Flow? = null } 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 b35c110892d..4f16231747c 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 @@ -75,6 +75,7 @@ sealed class OnboardingAction : VectorViewModelAction { data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction() + object PersonalizeProfile : OnboardingAction() data class UpdateDisplayName(val displayName: String) : OnboardingAction() object UpdateDisplayNameSkipped : OnboardingAction() data class ProfilePictureSelected(val uri: Uri) : OnboardingAction() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index 8a09879b15a..82ee48411d6 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -51,9 +51,8 @@ sealed class OnboardingViewEvents : VectorViewEvents { object OnAccountCreated : OnboardingViewEvents() object OnAccountSignedIn : OnboardingViewEvents() object OnTakeMeHome : OnboardingViewEvents() - object OnPersonalizeProfile : OnboardingViewEvents() - object OnDisplayNameUpdated : OnboardingViewEvents() - object OnDisplayNameSkipped : OnboardingViewEvents() + object OnChooseDisplayName : OnboardingViewEvents() + object OnChooseProfilePicture : OnboardingViewEvents() object OnPersonalizationComplete : OnboardingViewEvents() object OnBack : OnboardingViewEvents() } 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 413745f98c8..36020fbe61f 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 @@ -48,6 +48,7 @@ import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService @@ -156,12 +157,13 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.ResetAction -> handleResetAction(action) is OnboardingAction.UserAcceptCertificate -> handleUserAcceptCertificate(action) OnboardingAction.ClearHomeServerHistory -> handleClearHomeServerHistory() - is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent) is OnboardingAction.UpdateDisplayName -> updateDisplayName(action.displayName) - OnboardingAction.UpdateDisplayNameSkipped -> _viewEvents.post(OnboardingViewEvents.OnDisplayNameSkipped) - OnboardingAction.UpdateProfilePictureSkipped -> _viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete) + OnboardingAction.UpdateDisplayNameSkipped -> handleDisplayNameStepComplete() + OnboardingAction.UpdateProfilePictureSkipped -> completePersonalization() + OnboardingAction.PersonalizeProfile -> handlePersonalizeProfile() is OnboardingAction.ProfilePictureSelected -> handleProfilePictureSelected(action) OnboardingAction.SaveSelectedProfilePicture -> updateProfilePicture() + is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent) }.exhaustive } @@ -762,15 +764,33 @@ class OnboardingViewModel @AssistedInject constructor( authenticationService.reset() session.configureAndStart(applicationContext) - setState { - copy( - asyncLoginAction = Success(Unit) - ) - } when (isAccountCreated) { - true -> _viewEvents.post(OnboardingViewEvents.OnAccountCreated) - false -> _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) + true -> { + val personalizationState = createPersonalizationState(session, state) + setState { + copy(asyncLoginAction = Success(Unit), personalizationState = personalizationState) + } + _viewEvents.post(OnboardingViewEvents.OnAccountCreated) + } + false -> { + setState { copy(asyncLoginAction = Success(Unit)) } + _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) + } + } + } + + private suspend fun createPersonalizationState(session: Session, state: OnboardingViewState): PersonalizationState { + return when { + vectorFeatures.isOnboardingPersonalizeEnabled() -> { + val homeServerCapabilities = session.getHomeServerCapabilities() + val capabilityOverrides = vectorOverrides.forceHomeserverCapabilities?.firstOrNull() + state.personalizationState.copy( + supportsChangingDisplayName = capabilityOverrides?.canChangeDisplayName ?: homeServerCapabilities.canChangeDisplayName, + supportsChangingProfilePicture = capabilityOverrides?.canChangeAvatar ?: homeServerCapabilities.canChangeAvatar + ) + } + else -> state.personalizationState } } @@ -910,7 +930,7 @@ class OnboardingViewModel @AssistedInject constructor( personalizationState = personalizationState.copy(displayName = displayName) ) } - _viewEvents.post(OnboardingViewEvents.OnDisplayNameUpdated) + handleDisplayNameStepComplete() } catch (error: Throwable) { setState { copy(asyncDisplayName = Fail(error)) } _viewEvents.post(OnboardingViewEvents.Failure(error)) @@ -918,12 +938,37 @@ class OnboardingViewModel @AssistedInject constructor( } } + private fun handlePersonalizeProfile() { + withPersonalisationState { + when { + it.supportsChangingDisplayName -> _viewEvents.post(OnboardingViewEvents.OnChooseDisplayName) + it.supportsChangingProfilePicture -> _viewEvents.post(OnboardingViewEvents.OnChooseProfilePicture) + else -> { + throw IllegalStateException("It should not be possible to personalize without supporting display name or avatar changing") + } + } + } + } + + private fun handleDisplayNameStepComplete() { + withPersonalisationState { + when { + it.supportsChangingProfilePicture -> _viewEvents.post(OnboardingViewEvents.OnChooseProfilePicture) + else -> completePersonalization() + } + } + } + private fun handleProfilePictureSelected(action: OnboardingAction.ProfilePictureSelected) { setState { copy(personalizationState = personalizationState.copy(selectedPictureUri = action.uri)) } } + private fun withPersonalisationState(block: (PersonalizationState) -> Unit) { + withState { block(it.personalizationState) } + } + private fun updateProfilePicture() { withState { state -> when (val pictureUri = state.personalizationState.selectedPictureUri) { @@ -955,6 +1000,10 @@ class OnboardingViewModel @AssistedInject constructor( } private fun onProfilePictureSaved() { + completePersonalization() + } + + private fun completePersonalization() { _viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete) } } 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 bd5d93ae4d1..8747de6da89 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 @@ -22,7 +22,6 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.PersistState -import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ServerType @@ -83,10 +82,6 @@ data class OnboardingViewState( asyncDisplayName is Loading || asyncProfilePicture is Loading } - - fun isAuthTaskCompleted(): Boolean { - return asyncLoginAction is Success - } } enum class OnboardingFlow { @@ -97,6 +92,11 @@ enum class OnboardingFlow { @Parcelize data class PersonalizationState( + val supportsChangingDisplayName: Boolean = false, + val supportsChangingProfilePicture: Boolean = false, val displayName: String? = null, val selectedPictureUri: Uri? = null -) : Parcelable +) : Parcelable { + + fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt index d021fd28131..ccfb863a5b1 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt @@ -20,11 +20,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible 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 im.vector.app.features.onboarding.OnboardingViewEvents +import im.vector.app.features.onboarding.OnboardingViewState import javax.inject.Inject class FtueAuthAccountCreatedFragment @Inject constructor( @@ -42,8 +44,15 @@ class FtueAuthAccountCreatedFragment @Inject constructor( private fun setupViews() { views.accountCreatedSubtitle.text = getString(R.string.ftue_account_created_subtitle, activeSessionHolder.getActiveSession().myUserId) - views.accountCreatedPersonalize.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnPersonalizeProfile)) } + views.accountCreatedPersonalize.debouncedClicks { viewModel.handle(OnboardingAction.PersonalizeProfile) } views.accountCreatedTakeMeHome.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) } + views.accountCreatedTakeMeHomeCta.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) } + } + + override fun updateWithState(state: OnboardingViewState) { + val canPersonalize = state.personalizationState.supportsPersonalization() + views.personalizeButtonGroup.isVisible = canPersonalize + views.takeMeHomeButtonGroup.isVisible = !canPersonalize } override fun resetViewModel() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt index bc1bf0c8bce..81300932db7 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.core.view.isInvisible import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder @@ -70,6 +71,8 @@ class FtueAuthChooseProfilePictureFragment @Inject constructor( } override fun updateWithState(state: OnboardingViewState) { + views.profilePictureToolbar.isInvisible = !state.personalizationState.supportsChangingDisplayName + val hasSetPicture = state.personalizationState.selectedPictureUri != null views.profilePictureSubmit.isEnabled = hasSetPicture views.changeProfilePictureIcon.setImageResource(if (hasSetPicture) R.drawable.ic_edit else R.drawable.ic_camera_plain) @@ -93,4 +96,14 @@ class FtueAuthChooseProfilePictureFragment @Inject constructor( override fun resetViewModel() { // Nothing to do } + + override fun onBackPressed(toolbarButton: Boolean): Boolean { + return when (withState(viewModel) { it.personalizationState.supportsChangingDisplayName }) { + true -> super.onBackPressed(toolbarButton) + false -> { + viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) + true + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index e336419e3f0..2008726ac34 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -122,17 +122,9 @@ class FtueAuthVariant( private fun updateWithState(viewState: OnboardingViewState) { isForceLoginFallbackEnabled = viewState.isForceLoginFallbackEnabled - views.loginLoading.isVisible = shouldShowLoading(viewState) + views.loginLoading.isVisible = viewState.isLoading() } - private fun shouldShowLoading(viewState: OnboardingViewState) = - 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() - } - override fun setIsLoading(isLoading: Boolean) = Unit private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) { @@ -230,12 +222,11 @@ class FtueAuthVariant( FtueAuthUseCaseFragment::class.java, option = commonOption) } - OnboardingViewEvents.OnAccountCreated -> onAccountCreated() + is OnboardingViewEvents.OnAccountCreated -> onAccountCreated() OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn() - OnboardingViewEvents.OnPersonalizeProfile -> onPersonalizeProfile() + OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName() OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true) - OnboardingViewEvents.OnDisplayNameUpdated -> onDisplayNameUpdated() - OnboardingViewEvents.OnDisplayNameSkipped -> onDisplayNameUpdated() + OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture() OnboardingViewEvents.OnPersonalizationComplete -> navigateToHome(createdAccount = true) OnboardingViewEvents.OnBack -> activity.popBackstack() }.exhaustive @@ -399,15 +390,11 @@ class FtueAuthVariant( } 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) - } + activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + activity.replaceFragment( + views.loginFragmentContainer, + FtueAuthAccountCreatedFragment::class.java + ) } private fun navigateToHome(createdAccount: Boolean) { @@ -416,14 +403,14 @@ class FtueAuthVariant( activity.finish() } - private fun onPersonalizeProfile() { + private fun onChooseDisplayName() { activity.addFragmentToBackstack(views.loginFragmentContainer, FtueAuthChooseDisplayNameFragment::class.java, option = commonOption ) } - private fun onDisplayNameUpdated() { + private fun onChooseProfilePicture() { activity.addFragmentToBackstack(views.loginFragmentContainer, FtueAuthChooseProfilePictureFragment::class.java, option = commonOption diff --git a/vector/src/main/res/layout/fragment_ftue_account_created.xml b/vector/src/main/res/layout/fragment_ftue_account_created.xml index 1985af1d5ee..65bcdf2b639 100644 --- a/vector/src/main/res/layout/fragment_ftue_account_created.xml +++ b/vector/src/main/res/layout/fragment_ftue_account_created.xml @@ -86,6 +86,14 @@ app:layout_constraintBottom_toTopOf="@id/accountCreatedPersonalize" app:layout_constraintTop_toBottomOf="@id/accountCreatedSubtitle" /> + +