Skip to content

Commit 2790506

Browse files
authored
Merge pull request #5325 from vector-im/feature/eric/registration-feature-flag
#5307 Adds ForceLoginFallback feature flag to Login and Registration
2 parents 8d5d064 + 65242df commit 2790506

File tree

11 files changed

+134
-69
lines changed

11 files changed

+134
-69
lines changed

changelog.d/5325.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Adds forceLoginFallback feature flag and usages to FTUE login and registration

vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class DebugFeaturesStateFactory @Inject constructor(
5353
label = "FTUE Personalize profile",
5454
key = DebugFeatureKeys.onboardingPersonalize,
5555
factory = VectorFeatures::isOnboardingPersonalizeEnabled
56-
)
56+
),
5757
))
5858
}
5959

vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt

+4
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ class DebugPrivateSettingsFragment : VectorBaseFragment<FragmentDebugPrivateSett
4343
views.forceDialPadTabDisplay.setOnCheckedChangeListener { _, isChecked ->
4444
viewModel.handle(DebugPrivateSettingsViewActions.SetDialPadVisibility(isChecked))
4545
}
46+
views.forceLoginFallback.setOnCheckedChangeListener { _, isChecked ->
47+
viewModel.handle(DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled(isChecked))
48+
}
4649
}
4750

4851
override fun invalidate() = withState(viewModel) {
4952
views.forceDialPadTabDisplay.isChecked = it.dialPadVisible
53+
views.forceLoginFallback.isChecked = it.forceLoginFallback
5054
}
5155
}

vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt

+1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
2020

2121
sealed class DebugPrivateSettingsViewActions : VectorViewModelAction {
2222
data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions()
23+
data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions()
2324
}

vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt

+13-4
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,18 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
4545

4646
private fun observeVectorDataStore() {
4747
vectorDataStore.forceDialPadDisplayFlow.setOnEach {
48-
copy(
49-
dialPadVisible = it
50-
)
48+
copy(dialPadVisible = it)
49+
}
50+
51+
vectorDataStore.forceLoginFallbackFlow.setOnEach {
52+
copy(forceLoginFallback = it)
5153
}
5254
}
5355

5456
override fun handle(action: DebugPrivateSettingsViewActions) {
5557
when (action) {
56-
is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action)
58+
is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action)
59+
is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action)
5760
}
5861
}
5962

@@ -62,4 +65,10 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
6265
vectorDataStore.setForceDialPadDisplay(action.force)
6366
}
6467
}
68+
69+
private fun handleSetForceLoginFallbackEnabled(action: DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled) {
70+
viewModelScope.launch {
71+
vectorDataStore.setForceLoginFallbackFlow(action.force)
72+
}
73+
}
6574
}

vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ package im.vector.app.features.debug.settings
1919
import com.airbnb.mvrx.MavericksState
2020

2121
data class DebugPrivateSettingsViewState(
22-
val dialPadVisible: Boolean = false
22+
val dialPadVisible: Boolean = false,
23+
val forceLoginFallback: Boolean = false,
2324
) : MavericksState

vector/src/debug/res/layout/fragment_debug_private_settings.xml

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
android:layout_height="wrap_content"
2626
android:text="Force DialPad tab display" />
2727

28+
<CheckBox
29+
android:id="@+id/forceLoginFallback"
30+
android:layout_width="wrap_content"
31+
android:layout_height="wrap_content"
32+
android:text="Force login and registration fallback" />
33+
2834
</LinearLayout>
2935

3036
</ScrollView>

vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt

+10-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import im.vector.app.features.login.LoginMode
4646
import im.vector.app.features.login.ReAuthHelper
4747
import im.vector.app.features.login.ServerType
4848
import im.vector.app.features.login.SignMode
49+
import im.vector.app.features.settings.VectorDataStore
4950
import kotlinx.coroutines.Job
5051
import kotlinx.coroutines.launch
5152
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
@@ -78,7 +79,8 @@ class OnboardingViewModel @AssistedInject constructor(
7879
private val stringProvider: StringProvider,
7980
private val homeServerHistoryService: HomeServerHistoryService,
8081
private val vectorFeatures: VectorFeatures,
81-
private val analyticsTracker: AnalyticsTracker
82+
private val analyticsTracker: AnalyticsTracker,
83+
private val vectorDataStore: VectorDataStore,
8284
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
8385

8486
@AssistedFactory
@@ -90,6 +92,7 @@ class OnboardingViewModel @AssistedInject constructor(
9092

9193
init {
9294
getKnownCustomHomeServersUrls()
95+
observeDataStore()
9396
}
9497

9598
private fun getKnownCustomHomeServersUrls() {
@@ -98,6 +101,12 @@ class OnboardingViewModel @AssistedInject constructor(
98101
}
99102
}
100103

104+
private fun observeDataStore() = viewModelScope.launch {
105+
vectorDataStore.forceLoginFallbackFlow.setOnEach { isForceLoginFallbackEnabled ->
106+
copy(isForceLoginFallbackEnabled = isForceLoginFallbackEnabled)
107+
}
108+
}
109+
101110
// Store the last action, to redo it after user has trusted the untrusted certificate
102111
private var lastAction: OnboardingAction? = null
103112
private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null

vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ data class OnboardingViewState(
6262
// Supported types for the login. We cannot use a sealed class for LoginType because it is not serializable
6363
@PersistState
6464
val loginModeSupportedTypes: List<String> = emptyList(),
65-
val knownCustomHomeServersUrls: List<String> = emptyList()
65+
val knownCustomHomeServersUrls: List<String> = emptyList(),
66+
val isForceLoginFallbackEnabled: Boolean = false,
6667
) : MavericksState {
6768

6869
fun isLoading(): Boolean {

vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt

+82-61
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class FtueAuthVariant(
7575
private val popEnterAnim = R.anim.no_anim
7676
private val popExitAnim = R.anim.exit_fade_out
7777

78+
private var isForceLoginFallbackEnabled = false
79+
7880
private val topFragment: Fragment?
7981
get() = supportFragmentManager.findFragmentById(views.loginFragmentContainer.id)
8082

@@ -109,10 +111,6 @@ class FtueAuthVariant(
109111
}
110112
}
111113

112-
override fun setIsLoading(isLoading: Boolean) {
113-
// do nothing
114-
}
115-
116114
private fun addFirstFragment() {
117115
val splashFragment = when (vectorFeatures.isOnboardingSplashCarouselEnabled()) {
118116
true -> FtueAuthSplashCarouselFragment::class.java
@@ -121,11 +119,25 @@ class FtueAuthVariant(
121119
activity.addFragment(views.loginFragmentContainer, splashFragment)
122120
}
123121

122+
private fun updateWithState(viewState: OnboardingViewState) {
123+
isForceLoginFallbackEnabled = viewState.isForceLoginFallbackEnabled
124+
views.loginLoading.isVisible = shouldShowLoading(viewState)
125+
}
126+
127+
private fun shouldShowLoading(viewState: OnboardingViewState) =
128+
if (vectorFeatures.isOnboardingPersonalizeEnabled()) {
129+
viewState.isLoading()
130+
} else {
131+
// Keep loading when during success because of the delay when switching to the next Activity
132+
viewState.isLoading() || viewState.isAuthTaskCompleted()
133+
}
134+
135+
override fun setIsLoading(isLoading: Boolean) = Unit
136+
124137
private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) {
125138
when (viewEvents) {
126139
is OnboardingViewEvents.RegistrationFlowResult -> {
127-
// Check that all flows are supported by the application
128-
if (viewEvents.flowResult.missingStages.any { !it.isSupported() }) {
140+
if (registrationShouldFallback(viewEvents)) {
129141
// Display a popup to propose use web fallback
130142
onRegistrationStageNotSupported()
131143
} else {
@@ -136,11 +148,7 @@ class FtueAuthVariant(
136148
// First ask for login and password
137149
// I add a tag to indicate that this fragment is a registration stage.
138150
// This way it will be automatically popped in when starting the next registration stage
139-
activity.addFragmentToBackstack(views.loginFragmentContainer,
140-
FtueAuthLoginFragment::class.java,
141-
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
142-
option = commonOption
143-
)
151+
openAuthLoginFragmentWithTag(FRAGMENT_REGISTRATION_STAGE_TAG)
144152
}
145153
}
146154
}
@@ -228,13 +236,23 @@ class FtueAuthVariant(
228236
}.exhaustive
229237
}
230238

231-
private fun updateWithState(viewState: OnboardingViewState) {
232-
views.loginLoading.isVisible = if (vectorFeatures.isOnboardingPersonalizeEnabled()) {
233-
viewState.isLoading()
234-
} else {
235-
// Keep loading when during success because of the delay when switching to the next Activity
236-
viewState.isLoading() || viewState.isAuthTaskCompleted()
237-
}
239+
private fun registrationShouldFallback(registrationFlowResult: OnboardingViewEvents.RegistrationFlowResult) =
240+
isForceLoginFallbackEnabled || registrationFlowResult.containsUnsupportedRegistrationFlow()
241+
242+
private fun OnboardingViewEvents.RegistrationFlowResult.containsUnsupportedRegistrationFlow() =
243+
flowResult.missingStages.any { !it.isSupported() }
244+
245+
private fun onRegistrationStageNotSupported() {
246+
MaterialAlertDialogBuilder(activity)
247+
.setTitle(R.string.app_name)
248+
.setMessage(activity.getString(R.string.login_registration_not_supported))
249+
.setPositiveButton(R.string.yes) { _, _ ->
250+
activity.addFragmentToBackstack(views.loginFragmentContainer,
251+
FtueAuthWebFragment::class.java,
252+
option = commonOption)
253+
}
254+
.setNegativeButton(R.string.no, null)
255+
.show()
238256
}
239257

240258
private fun onWebLoginError(onWebLoginError: OnboardingViewEvents.OnWebLoginError) {
@@ -264,64 +282,67 @@ class FtueAuthVariant(
264282
// state.signMode could not be ready yet. So use value from the ViewEvent
265283
when (OnboardingViewEvents.signMode) {
266284
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
267-
SignMode.SignUp -> {
268-
// This is managed by the OnboardingViewEvents
269-
}
270-
SignMode.SignIn -> {
271-
// It depends on the LoginMode
272-
when (state.loginMode) {
273-
LoginMode.Unknown,
274-
is LoginMode.Sso -> error("Developer error")
275-
is LoginMode.SsoAndPassword,
276-
LoginMode.Password -> activity.addFragmentToBackstack(views.loginFragmentContainer,
277-
FtueAuthLoginFragment::class.java,
278-
tag = FRAGMENT_LOGIN_TAG,
279-
option = commonOption)
280-
LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
281-
}.exhaustive
282-
}
283-
SignMode.SignInWithMatrixId -> activity.addFragmentToBackstack(views.loginFragmentContainer,
284-
FtueAuthLoginFragment::class.java,
285-
tag = FRAGMENT_LOGIN_TAG,
286-
option = commonOption)
285+
SignMode.SignUp -> Unit // This case is processed in handleOnboardingViewEvents
286+
SignMode.SignIn -> handleSignInSelected(state)
287+
SignMode.SignInWithMatrixId -> handleSignInWithMatrixId(state)
287288
}.exhaustive
288289
}
289290

290-
/**
291-
* Handle the SSO redirection here
292-
*/
293-
override fun onNewIntent(intent: Intent?) {
294-
intent?.data
295-
?.let { tryOrNull { it.getQueryParameter("loginToken") } }
296-
?.let { onboardingViewModel.handle(OnboardingAction.LoginWithToken(it)) }
291+
private fun handleSignInSelected(state: OnboardingViewState) {
292+
if (isForceLoginFallbackEnabled) {
293+
onLoginModeNotSupported(state.loginModeSupportedTypes)
294+
} else {
295+
disambiguateLoginMode(state)
296+
}
297297
}
298298

299-
private fun onRegistrationStageNotSupported() {
300-
MaterialAlertDialogBuilder(activity)
301-
.setTitle(R.string.app_name)
302-
.setMessage(activity.getString(R.string.login_registration_not_supported))
303-
.setPositiveButton(R.string.yes) { _, _ ->
304-
activity.addFragmentToBackstack(views.loginFragmentContainer,
305-
FtueAuthWebFragment::class.java,
306-
option = commonOption)
307-
}
308-
.setNegativeButton(R.string.no, null)
309-
.show()
299+
private fun disambiguateLoginMode(state: OnboardingViewState) = when (state.loginMode) {
300+
LoginMode.Unknown,
301+
is LoginMode.Sso -> error("Developer error")
302+
is LoginMode.SsoAndPassword,
303+
LoginMode.Password -> openAuthLoginFragmentWithTag(FRAGMENT_LOGIN_TAG)
304+
LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
305+
}
306+
307+
private fun openAuthLoginFragmentWithTag(tag: String) {
308+
activity.addFragmentToBackstack(views.loginFragmentContainer,
309+
FtueAuthLoginFragment::class.java,
310+
tag = tag,
311+
option = commonOption)
310312
}
311313

312314
private fun onLoginModeNotSupported(supportedTypes: List<String>) {
313315
MaterialAlertDialogBuilder(activity)
314316
.setTitle(R.string.app_name)
315317
.setMessage(activity.getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" }))
316-
.setPositiveButton(R.string.yes) { _, _ ->
317-
activity.addFragmentToBackstack(views.loginFragmentContainer,
318-
FtueAuthWebFragment::class.java,
319-
option = commonOption)
320-
}
318+
.setPositiveButton(R.string.yes) { _, _ -> openAuthWebFragment() }
321319
.setNegativeButton(R.string.no, null)
322320
.show()
323321
}
324322

323+
private fun handleSignInWithMatrixId(state: OnboardingViewState) {
324+
if (isForceLoginFallbackEnabled) {
325+
onLoginModeNotSupported(state.loginModeSupportedTypes)
326+
} else {
327+
openAuthLoginFragmentWithTag(FRAGMENT_LOGIN_TAG)
328+
}
329+
}
330+
331+
private fun openAuthWebFragment() {
332+
activity.addFragmentToBackstack(views.loginFragmentContainer,
333+
FtueAuthWebFragment::class.java,
334+
option = commonOption)
335+
}
336+
337+
/**
338+
* Handle the SSO redirection here
339+
*/
340+
override fun onNewIntent(intent: Intent?) {
341+
intent?.data
342+
?.let { tryOrNull { it.getQueryParameter("loginToken") } }
343+
?.let { onboardingViewModel.handle(OnboardingAction.LoginWithToken(it)) }
344+
}
345+
325346
private fun handleRegistrationNavigation(flowResult: FlowResult) {
326347
// Complete all mandatory stages first
327348
val mandatoryStage = flowResult.missingStages.firstOrNull { it.mandatory }

vector/src/main/java/im/vector/app/features/settings/VectorDataStore.kt

+12
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,16 @@ class VectorDataStore @Inject constructor(
5959
settings[forceDialPadDisplay] = force
6060
}
6161
}
62+
63+
private val forceLoginFallback = booleanPreferencesKey("force_login_fallback")
64+
65+
val forceLoginFallbackFlow: Flow<Boolean> = context.dataStore.data.map { preferences ->
66+
preferences[forceLoginFallback].orFalse()
67+
}
68+
69+
suspend fun setForceLoginFallbackFlow(force: Boolean) {
70+
context.dataStore.edit { settings ->
71+
settings[forceLoginFallback] = force
72+
}
73+
}
6274
}

0 commit comments

Comments
 (0)