dynamically switching the onboarding flow based on the capabilities of the homeserver

- when avatars can't be changed we complete the personlisation flow
This commit is contained in:
Adam Brown 2022-02-25 16:59:20 +00:00
parent 46be481eda
commit 716928d9d2
6 changed files with 66 additions and 25 deletions

View file

@ -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()

View file

@ -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()
}

View file

@ -81,7 +81,6 @@ class OnboardingViewModel @AssistedInject constructor(
private val stringProvider: StringProvider,
private val homeServerHistoryService: HomeServerHistoryService,
private val vectorFeatures: VectorFeatures,
private val vectorOverrides: VectorOverrides,
private val analyticsTracker: AnalyticsTracker,
private val uriFilenameResolver: UriFilenameResolver,
private val vectorOverrides: VectorOverrides
@ -158,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.UpdateDisplayNameSkipped -> handleDisplayNameStepComplete()
OnboardingAction.UpdateProfilePictureSkipped -> _viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete)
OnboardingAction.PersonalizeProfile -> handlePersonalizeProfile()
is OnboardingAction.ProfilePictureSelected -> handleProfilePictureSelected(action)
OnboardingAction.SaveSelectedProfilePicture -> updateProfilePicture()
is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
}.exhaustive
}
@ -921,7 +921,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))
@ -929,12 +929,35 @@ class OnboardingViewModel @AssistedInject constructor(
}
}
private fun handlePersonalizeProfile() {
withPersonalisationState {
when {
it.supportsChangingDisplayName -> _viewEvents.post(OnboardingViewEvents.OnChooseDisplayName)
it.supportsChangingProfilePicture -> _viewEvents.post(OnboardingViewEvents.OnChooseDisplayName)
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 -> _viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete)
}
}
}
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) {

View file

@ -44,7 +44,7 @@ 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)) }
}

View file

@ -232,10 +232,9 @@ class FtueAuthVariant(
}
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
@ -412,14 +411,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

View file

@ -36,7 +36,6 @@ 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
@ -111,21 +110,31 @@ class OnboardingViewModelTest {
}
@Test
fun `when handling display name update then updates upstream user display name`() = runBlockingTest {
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)
}
@ -227,7 +236,6 @@ class OnboardingViewModelTest {
FakeStringProvider().instance,
FakeHomeServerHistoryService(),
FakeVectorFeatures(),
FakeVectorOverrides(),
FakeAnalyticsTracker(),
fakeUriFilenameResolver.instance,
DefaultVectorOverrides()
@ -259,4 +267,15 @@ class OnboardingViewModelTest {
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)
)
)
}
}