mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Merge pull request #5375 from vector-im/feature/adm/display-personalisation-based-on-capabilities
FTUE - Capability based personalisation flow
This commit is contained in:
commit
82e1afdb72
1
changelog.d/5375.wip
Normal file
1
changelog.d/5375.wip
Normal file
@ -0,0 +1 @@
|
||||
Dynamically showing/hiding onboarding personalisation screens based on the users homeserver capabilities
|
@ -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<Preferences> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,12 @@ class DebugPrivateSettingsFragment : VectorBaseFragment<FragmentDebugPrivateSett
|
||||
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
views.forceDialPadTabDisplay.isChecked = it.dialPadVisible
|
||||
views.forceChangeDisplayNameCapability.bind(it.homeserverCapabilityOverrides.displayName) { option ->
|
||||
viewModel.handle(DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride(option))
|
||||
}
|
||||
views.forceChangeAvatarCapability.bind(it.homeserverCapabilityOverrides.avatar) { option ->
|
||||
viewModel.handle(DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride(option))
|
||||
}
|
||||
views.forceLoginFallback.isChecked = it.forceLoginFallback
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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<DebugPrivateSettingsViewModel, DebugPrivateSettingsViewState> 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<BooleanHomeserverCapabilitiesOverride> = OverrideDropdown(
|
||||
label = "Override display name capability",
|
||||
activeOption = null,
|
||||
options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled)
|
||||
),
|
||||
val avatar: OverrideDropdown<BooleanHomeserverCapabilitiesOverride> = OverrideDropdown(
|
||||
label = "Override avatar capability",
|
||||
activeOption = null,
|
||||
options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled)
|
||||
)
|
||||
)
|
||||
|
@ -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 <T : OverrideOption> bind(feature: OverrideDropdown<T>, listener: Listener<T>) {
|
||||
binding.overrideLabel.text = feature.label
|
||||
|
||||
binding.overrideOptions.apply {
|
||||
val arrayAdapter = ArrayAdapter<String>(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<T> {
|
||||
fun onOverrideSelected(option: T?)
|
||||
}
|
||||
|
||||
data class OverrideDropdown<T : OverrideOption>(
|
||||
val label: String,
|
||||
val options: List<T>,
|
||||
val activeOption: T?,
|
||||
)
|
||||
}
|
||||
|
||||
interface OverrideOption {
|
||||
val label: String
|
||||
}
|
@ -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
|
||||
}
|
@ -31,6 +31,24 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Force login and registration fallback" />
|
||||
|
||||
<im.vector.app.features.debug.settings.OverrideDropdownView
|
||||
android:id="@+id/forceChangeDisplayNameCapability"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<im.vector.app.features.debug.settings.OverrideDropdownView
|
||||
android:id="@+id/forceChangeAvatarCapability"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
25
vector/src/debug/res/layout/view_boolean_dropdown.xml
Normal file
25
vector/src/debug/res/layout/view_boolean_dropdown.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/overrideLabel"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:textColor="?vctr_content_primary"
|
||||
tools:text="Login version" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/overrideOptions"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</merge>
|
@ -22,9 +22,16 @@ import kotlinx.coroutines.flow.flowOf
|
||||
interface VectorOverrides {
|
||||
val forceDialPad: Flow<Boolean>
|
||||
val forceLoginFallback: Flow<Boolean>
|
||||
val forceHomeserverCapabilities: Flow<HomeserverCapabilitiesOverride>?
|
||||
}
|
||||
|
||||
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<HomeserverCapabilitiesOverride>? = null
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -86,6 +86,14 @@
|
||||
app:layout_constraintBottom_toTopOf="@id/accountCreatedPersonalize"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountCreatedSubtitle" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/personalizeButtonGroup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="accountCreatedPersonalize,accountCreatedTakeMeHome"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/accountCreatedPersonalize"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
@ -96,11 +104,10 @@
|
||||
android:textAllCaps="true"
|
||||
android:textColor="?colorSecondary"
|
||||
android:transitionName="loginSubmitTransition"
|
||||
app:layout_constraintBottom_toTopOf="@id/accountCreatedSpace5"
|
||||
app:layout_constraintBottom_toTopOf="@id/accountCreatedTakeMeHome"
|
||||
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountCreatedSpace4"
|
||||
tools:text="@string/ftue_account_created_personalize" />
|
||||
app:layout_constraintTop_toBottomOf="@id/accountCreatedSpace4" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/accountCreatedTakeMeHome"
|
||||
@ -111,17 +118,46 @@
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/element_background_light"
|
||||
android:transitionName="loginSubmitTransition"
|
||||
app:layout_constraintBottom_toTopOf="@id/accountCreatedSpace5"
|
||||
app:layout_constraintBottom_toTopOf="@id/ctaBottomBarrier"
|
||||
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountCreatedPersonalize" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/takeMeHomeButtonGroup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="accountCreatedTakeMeHomeCta" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/accountCreatedTakeMeHomeCta"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/element_background_light"
|
||||
android:text="@string/ftue_account_created_take_me_home"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="?colorSecondary"
|
||||
android:transitionName="loginSubmitTransition"
|
||||
app:layout_constraintBottom_toTopOf="@id/ctaBottomBarrier"
|
||||
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountCreatedSpace4" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/ctaBottomBarrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="accountCreatedTakeMeHomeCta,accountCreatedTakeMeHome" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/accountCreatedSpace5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHeight_percent="0.05"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountCreatedPersonalize" />
|
||||
app:layout_constraintTop_toBottomOf="@id/ctaBottomBarrier" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -31,6 +31,7 @@
|
||||
style="@style/Widget.Vector.Toolbar.Settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toTopOf="@id/profilePictureView"
|
||||
app:layout_constraintTop_toBottomOf="@id/profilePictureToolbar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -20,8 +20,8 @@ import android.net.Uri
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.test.MvRxTestRule
|
||||
import im.vector.app.features.DefaultVectorOverrides
|
||||
import im.vector.app.features.login.ReAuthHelper
|
||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||
import im.vector.app.test.fakes.FakeAnalyticsTracker
|
||||
@ -29,20 +29,27 @@ import im.vector.app.test.fakes.FakeAuthenticationService
|
||||
import im.vector.app.test.fakes.FakeContext
|
||||
import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory
|
||||
import im.vector.app.test.fakes.FakeHomeServerHistoryService
|
||||
import im.vector.app.test.fakes.FakeRegistrationWizard
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
import im.vector.app.test.fakes.FakeUri
|
||||
import im.vector.app.test.fakes.FakeUriFilenameResolver
|
||||
import im.vector.app.test.fakes.FakeVectorFeatures
|
||||
import im.vector.app.test.fakes.FakeVectorOverrides
|
||||
import im.vector.app.test.test
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
|
||||
private const val A_DISPLAY_NAME = "a display name"
|
||||
private const val A_PICTURE_FILENAME = "a-picture.png"
|
||||
private val AN_ERROR = RuntimeException("an error!")
|
||||
private val AN_UNSUPPORTED_PERSONALISATION_STATE = PersonalizationState(
|
||||
supportsChangingDisplayName = false,
|
||||
supportsChangingProfilePicture = false
|
||||
)
|
||||
|
||||
class OnboardingViewModelTest {
|
||||
|
||||
@ -55,6 +62,7 @@ class OnboardingViewModelTest {
|
||||
private val fakeSession = FakeSession()
|
||||
private val fakeUriFilenameResolver = FakeUriFilenameResolver()
|
||||
private val fakeActiveSessionHolder = FakeActiveSessionHolder(fakeSession)
|
||||
private val fakeAuthenticationService = FakeAuthenticationService()
|
||||
|
||||
lateinit var viewModel: OnboardingViewModel
|
||||
|
||||
@ -75,21 +83,84 @@ class OnboardingViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when handling display name update then updates upstream user display name`() = runBlockingTest {
|
||||
fun `given supports changing display name when handling PersonalizeProfile then emits contents choose display name`() = runBlockingTest {
|
||||
val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = true, supportsChangingProfilePicture = false))
|
||||
viewModel = createViewModel(initialState)
|
||||
val test = viewModel.test(this)
|
||||
|
||||
viewModel.handle(OnboardingAction.PersonalizeProfile)
|
||||
|
||||
test
|
||||
.assertEvents(OnboardingViewEvents.OnChooseDisplayName)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given only supports changing profile picture when handling PersonalizeProfile then emits contents choose profile picture`() = runBlockingTest {
|
||||
val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = false, supportsChangingProfilePicture = true))
|
||||
viewModel = createViewModel(initialState)
|
||||
val test = viewModel.test(this)
|
||||
|
||||
viewModel.handle(OnboardingAction.PersonalizeProfile)
|
||||
|
||||
test
|
||||
.assertEvents(OnboardingViewEvents.OnChooseProfilePicture)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given homeserver does not support personalisation when registering account then updates state and emits account created event`() = runBlockingTest {
|
||||
fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(HomeServerCapabilities(canChangeDisplayName = false, canChangeAvatar = false))
|
||||
givenSuccessfullyCreatesAccount()
|
||||
val test = viewModel.test(this)
|
||||
|
||||
viewModel.handle(OnboardingAction.RegisterDummy)
|
||||
|
||||
test
|
||||
.assertStates(
|
||||
initialState,
|
||||
initialState.copy(asyncRegistration = Loading()),
|
||||
initialState.copy(
|
||||
asyncLoginAction = Success(Unit),
|
||||
asyncRegistration = Loading(),
|
||||
personalizationState = AN_UNSUPPORTED_PERSONALISATION_STATE
|
||||
),
|
||||
initialState.copy(
|
||||
asyncLoginAction = Success(Unit),
|
||||
asyncRegistration = Uninitialized,
|
||||
personalizationState = AN_UNSUPPORTED_PERSONALISATION_STATE
|
||||
)
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnAccountCreated)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given changing profile picture is supported when updating display name then updates upstream user display name and moves to choose profile picture`() = runBlockingTest {
|
||||
val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = true))
|
||||
viewModel = createViewModel(personalisedInitialState)
|
||||
val test = viewModel.test(this)
|
||||
|
||||
viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
|
||||
|
||||
test
|
||||
.assertStates(
|
||||
initialState,
|
||||
initialState.copy(asyncDisplayName = Loading()),
|
||||
initialState.copy(
|
||||
asyncDisplayName = Success(Unit),
|
||||
personalizationState = initialState.personalizationState.copy(displayName = A_DISPLAY_NAME)
|
||||
)
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnDisplayNameUpdated)
|
||||
.assertStates(expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState))
|
||||
.assertEvents(OnboardingViewEvents.OnChooseProfilePicture)
|
||||
.finish()
|
||||
fakeSession.fakeProfileService.verifyUpdatedName(fakeSession.myUserId, A_DISPLAY_NAME)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given changing profile picture is not supported when updating display name then updates upstream user display name and completes personalization`() = runBlockingTest {
|
||||
val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = false))
|
||||
viewModel = createViewModel(personalisedInitialState)
|
||||
val test = viewModel.test(this)
|
||||
|
||||
viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
|
||||
|
||||
test
|
||||
.assertStates(expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState))
|
||||
.assertEvents(OnboardingViewEvents.OnPersonalizationComplete)
|
||||
.finish()
|
||||
fakeSession.fakeProfileService.verifyUpdatedName(fakeSession.myUserId, A_DISPLAY_NAME)
|
||||
}
|
||||
@ -184,7 +255,7 @@ class OnboardingViewModelTest {
|
||||
return OnboardingViewModel(
|
||||
state,
|
||||
fakeContext.instance,
|
||||
FakeAuthenticationService(),
|
||||
fakeAuthenticationService,
|
||||
fakeActiveSessionHolder.instance,
|
||||
FakeHomeServerConnectionConfigFactory().instance,
|
||||
ReAuthHelper(),
|
||||
@ -193,7 +264,7 @@ class OnboardingViewModelTest {
|
||||
FakeVectorFeatures(),
|
||||
FakeAnalyticsTracker(),
|
||||
fakeUriFilenameResolver.instance,
|
||||
DefaultVectorOverrides()
|
||||
FakeVectorOverrides()
|
||||
)
|
||||
}
|
||||
|
||||
@ -214,4 +285,23 @@ class OnboardingViewModelTest {
|
||||
state.copy(asyncProfilePicture = Loading()),
|
||||
state.copy(asyncProfilePicture = Fail(cause))
|
||||
)
|
||||
|
||||
private fun givenSuccessfullyCreatesAccount() {
|
||||
fakeActiveSessionHolder.expectSetsActiveSession(fakeSession)
|
||||
val registrationWizard = FakeRegistrationWizard().also { it.givenSuccessfulDummy(fakeSession) }
|
||||
fakeAuthenticationService.givenRegistrationWizard(registrationWizard)
|
||||
fakeAuthenticationService.expectReset()
|
||||
fakeSession.expectStartsSyncing()
|
||||
}
|
||||
|
||||
private fun expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState: OnboardingViewState): List<OnboardingViewState> {
|
||||
return listOf(
|
||||
personalisedInitialState,
|
||||
personalisedInitialState.copy(asyncDisplayName = Loading()),
|
||||
personalisedInitialState.copy(
|
||||
asyncDisplayName = Success(Unit),
|
||||
personalizationState = personalisedInitialState.personalizationState.copy(displayName = A_DISPLAY_NAME)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ package im.vector.app.test.fakes
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
class FakeActiveSessionHolder(
|
||||
private val fakeSession: FakeSession = FakeSession()
|
||||
@ -26,4 +28,8 @@ class FakeActiveSessionHolder(
|
||||
val instance = mockk<ActiveSessionHolder> {
|
||||
every { getActiveSession() } returns fakeSession
|
||||
}
|
||||
|
||||
fun expectSetsActiveSession(session: Session) {
|
||||
justRun { instance.setActiveSession(session) }
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,18 @@
|
||||
|
||||
package im.vector.app.test.fakes
|
||||
|
||||
import io.mockk.coJustRun
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
|
||||
class FakeAuthenticationService : AuthenticationService by mockk()
|
||||
class FakeAuthenticationService : AuthenticationService by mockk() {
|
||||
fun givenRegistrationWizard(registrationWizard: RegistrationWizard) {
|
||||
every { getRegistrationWizard() } returns registrationWizard
|
||||
}
|
||||
|
||||
fun expectReset() {
|
||||
coJustRun { reset() }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.test.fakes
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||
|
||||
class FakeHomeServerCapabilitiesService : HomeServerCapabilitiesService by mockk() {
|
||||
|
||||
fun givenCapabilities(homeServerCapabilities: HomeServerCapabilities) {
|
||||
every { getHomeServerCapabilities() } returns homeServerCapabilities
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.test.fakes
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
class FakeRegistrationWizard : RegistrationWizard by mockk() {
|
||||
|
||||
fun givenSuccessfulDummy(session: Session) {
|
||||
coEvery { dummy() } returns RegistrationResult.Success(session)
|
||||
}
|
||||
}
|
@ -17,10 +17,13 @@
|
||||
package im.vector.app.test.fakes
|
||||
|
||||
import android.net.Uri
|
||||
import im.vector.app.core.extensions.configureAndStart
|
||||
import im.vector.app.core.extensions.startSyncing
|
||||
import im.vector.app.core.extensions.vectorStore
|
||||
import im.vector.app.features.session.VectorSessionStore
|
||||
import im.vector.app.test.testCoroutineDispatchers
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coJustRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
@ -28,6 +31,7 @@ import org.matrix.android.sdk.api.session.Session
|
||||
class FakeSession(
|
||||
val fakeCryptoService: FakeCryptoService = FakeCryptoService(),
|
||||
val fakeProfileService: FakeProfileService = FakeProfileService(),
|
||||
val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(),
|
||||
val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService()
|
||||
) : Session by mockk(relaxed = true) {
|
||||
|
||||
@ -42,6 +46,7 @@ class FakeSession(
|
||||
override val coroutineDispatchers = testCoroutineDispatchers
|
||||
override suspend fun setDisplayName(userId: String, newDisplayName: String) = fakeProfileService.setDisplayName(userId, newDisplayName)
|
||||
override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) = fakeProfileService.updateAvatar(userId, newAvatarUri, fileName)
|
||||
override fun getHomeServerCapabilities() = fakeHomeServerCapabilitiesService.getHomeServerCapabilities()
|
||||
|
||||
fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
|
||||
coEvery {
|
||||
@ -50,4 +55,11 @@ class FakeSession(
|
||||
vectorSessionStore
|
||||
}
|
||||
}
|
||||
|
||||
fun expectStartsSyncing() {
|
||||
coJustRun {
|
||||
this@FakeSession.configureAndStart(any(), startSyncing = true)
|
||||
this@FakeSession.startSyncing(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.test.fakes
|
||||
|
||||
import im.vector.app.features.DefaultVectorOverrides
|
||||
import im.vector.app.features.VectorOverrides
|
||||
|
||||
class FakeVectorOverrides : VectorOverrides by DefaultVectorOverrides()
|
Loading…
Reference in New Issue
Block a user