Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FTUE - Capability based personalisation flow #5375

Merged

Conversation

ouchadam
Copy link
Contributor

@ouchadam ouchadam commented Feb 28, 2022

Type of change

  • WIP Feature
  • Bugfix
  • Technical
  • Other :

Content

Draft PR, relies on #5361 #5323

Dynamically shows/hides the personalisation onboarding screen based on the homeservers capabilities.

when display name and profile picture are not supported

  • Account created only offers the Take me home button

when only changing display name is supported

  • Account created personalisation is enabled but only shows the display name step

when only changing profile picture is supported

  • Account created personalisation is enabled but only change profile picture step, removing the toolbar back to the match the display name flow

Also introduces debug overrides for the homeserver capabilities to make testing the different options easier.

Motivation and context

To only show personalisation options to users on homeservers which support them. Part of #5200

Screenshots / GIFs

ALL OPTIONS DISABLED ONLY DISPLAY NAME ONLY PROFILE PICTURE ALL ENABLED
all-disabled per-display-only per-only-picture pre-all-enabled

Tests

  • With a fresh install
  • Enable the Personalisation feature flag
  • Enable or disable forced homeserver capabilities within the private settings

Tested devices

  • Physical
  • Emulator
  • OS version(s): 29

@github-actions
Copy link

github-actions bot commented Feb 28, 2022

Unit Test Results

  98 files    98 suites   1m 22s ⏱️
178 tests 178 ✔️ 0 💤 0
584 runs  584 ✔️ 0 💤 0

Results for commit c06d3ff.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Feb 28, 2022

Matrix SDK

Integration Tests Results:

  • [org.matrix.android.sdk.session]
    =passed=6 failures=17 errors=0 skipped=2
  • [org.matrix.android.sdk.account]
    =passed=0 failures=1 errors=0 skipped=0
  • [org.matrix.android.sdk.internal]
    =passed=0 failures=1 errors=0 skipped=0
  • [org.matrix.android.sdk.ordering]
    =passed=0 failures=1 errors=0 skipped=0
  • [org.matrix.android.sdk.PermalinkParserTest]
    =passed=2 failures=0 errors=0 skipped=0

@ouchadam ouchadam mentioned this pull request Mar 1, 2022
6 tasks
@ouchadam ouchadam added the Z-FTUE Issue is relevant to the first time use project or experience label Mar 3, 2022
@ouchadam ouchadam force-pushed the feature/adm/display-personalisation-based-on-capabilities branch from c3c193d to 147de6e Compare March 7, 2022 16:43
@ouchadam ouchadam changed the base branch from feature/adm/tmp-base to develop March 7, 2022 16:43
@ouchadam ouchadam marked this pull request as ready for review March 7, 2022 16:44
@ouchadam ouchadam requested review from a team and mnaturel and removed request for a team March 10, 2022 09:15
import androidx.appcompat.widget.AppCompatSpinner
import im.vector.app.R

class OverrideDropdownView @JvmOverloads constructor(
Copy link
Contributor Author

@ouchadam ouchadam Mar 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a reusable dropdown view for the Debug menu -> Private settings (in the debug sourceset)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 👍 I am a big fan of custom views!

Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I did not review the PR, just add 2 small comments 🙈 !)

changelog.d/5375.wip Outdated Show resolved Hide resolved

<Button
android:id="@+id/accountCreatedTakeMeHomeCta"
style="@style/Widget.Vector.Button.Login"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a handy Widget.Vector.Button.CallToAction but it's used much (it's quite new). I am wondering if we should at some point use this style a bit more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to use the CTA style, especially when it's defined in the components library https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1970%3A25160

I'm tempted to update all the usages in one go in a separate PR rather than just this layout, what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a plan! (that was my original intention to make you think that 👿 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@mnaturel mnaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I have only done static code review. Only small remarks and questions.
Is there a doc I can find somewhere to learn how to test account creation with a local homeserver?

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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have never used DataStore API but I see this so much cleaner than the SharedPreferences API. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the main "downside" is that the api forces us to use suspend functions or flows (maybe a good thing from a performance standpoint 😄 )

@@ -51,4 +62,18 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides {
settings[keyForceLoginFallback] = force
}
}

suspend fun updateHomeserverCapabilities(block: HomeserverCapabilitiesOverride.() -> HomeserverCapabilitiesOverride) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could rename it to setHomeserverCapabilities to keep the same convention as the other methods?

Copy link
Contributor Author

@ouchadam ouchadam Mar 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, will do 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am just wondering why is the option BooleanHomeserverCapabilitiesOverride a nullable? Is this a case we should handle? I thought it would be either enabled or disabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's because the override allows the option to be both forced to disabled or enabled, null means don't override

2022-03-10T16:43:53,379476984+00:00

we could replace the 3 way dropdown with a checkbox wrapped with another checkbox but I felt using nullable would be less boilerplate~

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay it makes sense. I forgot it is an override setting.

@@ -51,4 +62,18 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides {
settings[keyForceLoginFallback] = force
}
}

suspend fun updateHomeserverCapabilities(block: HomeserverCapabilitiesOverride.() -> HomeserverCapabilitiesOverride) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we should switch to Dispatchers.io for write operations? Right now it uses implicitly the Dispatcher.main.immediate from the viewModelScope

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah good point, I was under the assumption the datastore api would internally switch, I'll double check!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init {
orientation = HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
inflate(context, R.layout.view_boolean_dropdown, this)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want you could migrate to ViewBinding like in this custom View.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do, thanks for the example 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

val canChangeDisplayName: Boolean?,
val canChangeAvatar: Boolean?
)

class DefaultVectorOverrides : VectorOverrides {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little confused about the purpose of DefaultVectorOverrides class. I see it is only used for testing purpose. Am I right? If it is the case, we may move it to the FakeVectorOverrides which is currently not used. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the missing piece of the puzzle here is that the default implementations are injected by the release variant of the app release module whereas the debug module allows for providing the runtime overridable version

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay thanks for explaining.

is OnboardingAction.UpdateDisplayName -> updateDisplayName(action.displayName)
OnboardingAction.UpdateDisplayNameSkipped -> _viewEvents.post(OnboardingViewEvents.OnDisplayNameSkipped)
OnboardingAction.UpdateDisplayNameSkipped -> handleDisplayNameStepComplete()
OnboardingAction.UpdateProfilePictureSkipped -> _viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could create a method onPersonalizationComplete to be used each time we need to post the event OnboardingViewEvents.OnPersonalizationComplete. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

happy to extract 👍 the reason I left it inlined was because there was no additional logic needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -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)) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are there 2 buttons accountCreatedTakeMeHome and accountCreatedTakeMeHomeCta? Is it to handle the 2 cases: can personalize and cannot?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes exactly ^^^ the screen is reused for both cases with slightly different button styling

PERSONALISE ENABLED PERSONALISE DISABLED
2022-03-10T17:07:47,476297091+00:00 2022-03-10T17:07:56,788929564+00:00

I could reuse the PERSONALIZE PROFILE button instead of creating a separate view, what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I would vote to have separate views, it's easier to apply different style in the layout, and also the action are not the same so could be more confusing if we update the text and the action depending on the state)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay for me as well to have 2 different views.

import im.vector.app.features.DefaultVectorOverrides
import im.vector.app.features.VectorOverrides

class FakeVectorOverrides : VectorOverrides by DefaultVectorOverrides()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is not used, is it normal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 nope, it's meant to be used here https://github.com/vector-im/element-android/pull/5375/files#diff-9bd5641dbabf55509a3dea8218d6d7d326781e4a23bf46867ec249eb640de34aR267

functionally the same but the abstraction enables reuse, will update 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is not used, is it normal?

good catch!

} else {
navigateToHome(createdAccount = true)
}
activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea, it would be cool if we could encapsulate all navigation code into a dedicated interface. It abstracts the navigation implementation and reduce the component responsability. Of course I don't suggest to change it in this PR 😄 But maybe later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have one for activities but not fragments, I think it's great idea and would help towards making this class testable

I've tracked it as an issue #5508

@ouchadam
Copy link
Contributor Author

ouchadam commented Mar 11, 2022

Nice! I have only done static code review. Only small remarks and questions. Is there a doc I can find somewhere to learn how to test account creation with a local homeserver?

there's nothing specific needed for testing account creation from the synapse standpoint, the easiest way to get setup is to run the demo script https://github.com/matrix-org/synapse#quick-start

Copy link
Contributor

@mnaturel mnaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't achieve to run the Synapse demo server on local (some strange config errors when starting server) to test the code. But I approve the PR since I don't see anything wrong in static.

@ouchadam
Copy link
Contributor Author

@mnaturel thanks for the review 👍 (and trying to run synapse locally, I'm happy to help out if you want to give it another go)

ouchadam and others added 15 commits March 14, 2022 11:54
…ser home if the display name personalisation is not supported
…f the homeserver

- when avatars can't be changed we complete the personlisation flow
…'t supported but pictures are when personalising the profile
… if personalization is supported

- also removes a legacy loading workaround for the account creation step, we're navigating to a new screen AccountCreated so we have to stop the loading
Co-authored-by: Benoit Marty <benoitm@matrix.org>
@ouchadam ouchadam force-pushed the feature/adm/display-personalisation-based-on-capabilities branch from 21222c1 to c06d3ff Compare March 14, 2022 12:58
@ouchadam ouchadam enabled auto-merge March 14, 2022 13:04
@ouchadam ouchadam merged commit 82e1afd into develop Mar 14, 2022
@ouchadam ouchadam deleted the feature/adm/display-personalisation-based-on-capabilities branch March 14, 2022 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Z-FTUE Issue is relevant to the first time use project or experience
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants