Skip to content

Commit e326188

Browse files
authored
Merge pull request #6108 from vector-im/feature/adm/ftue-msisdn-entry
FTUE - Msisdn (phone number) entry
2 parents 9a2beb5 + bfa50f1 commit e326188

18 files changed

+446
-21
lines changed

vector/src/main/java/im/vector/app/core/di/FragmentModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFrag
110110
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyWaitForEmailFragment
111111
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
112112
import im.vector.app.features.onboarding.ftueauth.FtueAuthPersonalizationCompleteFragment
113+
import im.vector.app.features.onboarding.ftueauth.FtueAuthPhoneEntryFragment
113114
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment
114115
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordMailConfirmationFragment
115116
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordSuccessFragment
@@ -509,6 +510,11 @@ interface FragmentModule {
509510
@FragmentKey(FtueAuthEmailEntryFragment::class)
510511
fun bindFtueAuthEmailEntryFragment(fragment: FtueAuthEmailEntryFragment): Fragment
511512

513+
@Binds
514+
@IntoMap
515+
@FragmentKey(FtueAuthPhoneEntryFragment::class)
516+
fun bindFtueAuthPhoneEntryFragment(fragment: FtueAuthPhoneEntryFragment): Fragment
517+
512518
@Binds
513519
@IntoMap
514520
@FragmentKey(FtueAuthChooseDisplayNameFragment::class)

vector/src/main/java/im/vector/app/core/di/SingletonModule.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.content.Context
2121
import android.content.Context.MODE_PRIVATE
2222
import android.content.SharedPreferences
2323
import android.content.res.Resources
24+
import com.google.i18n.phonenumbers.PhoneNumberUtil
2425
import dagger.Binds
2526
import dagger.Module
2627
import dagger.Provides
@@ -193,6 +194,9 @@ object VectorStaticModule {
193194
return analyticsConfig
194195
}
195196

197+
@Provides
198+
fun providesPhoneNumberUtil(): PhoneNumberUtil = PhoneNumberUtil.getInstance()
199+
196200
@Provides
197201
@Singleton
198202
fun providesBuildMeta() = BuildMeta()

vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
package im.vector.app.core.extensions
1818

19+
import android.os.Build
1920
import android.text.Editable
2021
import android.view.View
2122
import android.view.inputmethod.EditorInfo
23+
import androidx.autofill.HintConstants
2224
import androidx.lifecycle.LifecycleOwner
2325
import androidx.lifecycle.lifecycleScope
2426
import com.google.android.material.textfield.TextInputLayout
@@ -79,3 +81,12 @@ fun TextInputLayout.setOnFocusLostListener(action: () -> Unit) {
7981
}
8082
}
8183
}
84+
85+
fun TextInputLayout.autofillPhoneNumber() = setAutofillHint(HintConstants.AUTOFILL_HINT_PHONE_NUMBER)
86+
fun TextInputLayout.autofillEmail() = setAutofillHint(HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS)
87+
88+
private fun TextInputLayout.setAutofillHint(hintType: String) {
89+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
90+
setAutofillHints(hintType)
91+
}
92+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ class OnboardingViewModel @AssistedInject constructor(
305305
RegistrationActionHandler.Result.StartRegistration -> _viewEvents.post(OnboardingViewEvents.DisplayStartRegistration)
306306
RegistrationActionHandler.Result.UnsupportedStage -> _viewEvents.post(OnboardingViewEvents.DisplayRegistrationFallback)
307307
is RegistrationActionHandler.Result.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email))
308+
is RegistrationActionHandler.Result.SendMsisdnSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendMsisdnSuccess(it.msisdn.msisdn))
308309
is RegistrationActionHandler.Result.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause))
309310
RegistrationActionHandler.Result.MissingNextStage -> {
310311
_viewEvents.post(OnboardingViewEvents.Failure(IllegalStateException("No next registration stage found")))

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import im.vector.app.features.onboarding.ftueauth.MatrixOrgRegistrationStagesCom
2626
import kotlinx.coroutines.flow.first
2727
import org.matrix.android.sdk.api.auth.AuthenticationService
2828
import org.matrix.android.sdk.api.auth.registration.FlowResult
29+
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
2930
import org.matrix.android.sdk.api.auth.registration.Stage
3031
import org.matrix.android.sdk.api.session.Session
3132
import javax.inject.Inject
@@ -47,7 +48,8 @@ class RegistrationActionHandler @Inject constructor(
4748
else -> when (result) {
4849
is RegistrationResult.Complete -> Result.RegistrationComplete(result.session)
4950
is RegistrationResult.NextStep -> processFlowResult(result, state)
50-
is RegistrationResult.SendEmailSuccess -> Result.SendEmailSuccess(result.email)
51+
is RegistrationResult.SendEmailSuccess -> Result.SendEmailSuccess(result.email.email)
52+
is RegistrationResult.SendMsisdnSuccess -> Result.SendMsisdnSuccess(result.msisdn)
5153
is RegistrationResult.Error -> Result.Error(result.cause)
5254
}
5355
}
@@ -95,6 +97,7 @@ class RegistrationActionHandler @Inject constructor(
9597
data class NextStage(val stage: Stage) : Result
9698
data class Error(val cause: Throwable) : Result
9799
data class SendEmailSuccess(val email: String) : Result
100+
data class SendMsisdnSuccess(val msisdn: RegisterThreePid.Msisdn) : Result
98101
object MissingNextStage : Result
99102
object StartRegistration : Result
100103
object UnsupportedStage : Result

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class RegistrationWizardActionDelegate @Inject constructor(
5959
onSuccess = { it.toRegistrationResult() },
6060
onFailure = {
6161
when {
62-
action.threePid is RegisterThreePid.Email && it.is401() -> RegistrationResult.SendEmailSuccess(action.threePid.email)
62+
action.threePid is RegisterThreePid.Email && it.is401() -> RegistrationResult.SendEmailSuccess(action.threePid)
63+
action.threePid is RegisterThreePid.Msisdn && it.is401() -> RegistrationResult.SendMsisdnSuccess(action.threePid)
6364
else -> RegistrationResult.Error(it)
6465
}
6566
}
@@ -95,7 +96,8 @@ sealed interface RegistrationResult {
9596
data class Error(val cause: Throwable) : RegistrationResult
9697
data class Complete(val session: Session) : RegistrationResult
9798
data class NextStep(val flowResult: FlowResult) : RegistrationResult
98-
data class SendEmailSuccess(val email: String) : RegistrationResult
99+
data class SendEmailSuccess(val email: RegisterThreePid.Email) : RegistrationResult
100+
data class SendMsisdnSuccess(val msisdn: RegisterThreePid.Msisdn) : RegistrationResult
99101
}
100102

101103
sealed interface RegisterAction {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.view.LayoutInflater
2121
import android.view.View
2222
import android.view.ViewGroup
2323
import im.vector.app.core.extensions.associateContentStateWith
24+
import im.vector.app.core.extensions.autofillEmail
2425
import im.vector.app.core.extensions.clearErrorOnChange
2526
import im.vector.app.core.extensions.content
2627
import im.vector.app.core.extensions.isEmail
@@ -47,6 +48,7 @@ class FtueAuthEmailEntryFragment @Inject constructor() : AbstractFtueAuthFragmen
4748
views.emailEntryInput.setOnImeDoneListener { updateEmail() }
4849
views.emailEntryInput.clearErrorOnChange(viewLifecycleOwner)
4950
views.emailEntrySubmit.debouncedClicks { updateEmail() }
51+
views.emailEntryInput.autofillEmail()
5052
}
5153

5254
private fun updateEmail() {

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import im.vector.app.core.extensions.setTextOrHide
3636
import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding
3737
import im.vector.app.features.login.TextInputFormFragmentMode
3838
import im.vector.app.features.onboarding.OnboardingAction
39-
import im.vector.app.features.onboarding.OnboardingViewEvents
4039
import im.vector.app.features.onboarding.RegisterAction
4140
import kotlinx.coroutines.flow.launchIn
4241
import kotlinx.coroutines.flow.onEach
@@ -226,12 +225,7 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA
226225
views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
227226
}
228227
TextInputFormFragmentMode.SetMsisdn -> {
229-
if (throwable.is401()) {
230-
// This is normal use case, we go to the enter code screen
231-
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendMsisdnSuccess(viewModel.currentThreePid ?: "")))
232-
} else {
233-
views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
234-
}
228+
views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
235229
}
236230
TextInputFormFragmentMode.ConfirmMsisdn -> {
237231
when {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.onboarding.ftueauth
18+
19+
import android.os.Bundle
20+
import android.view.LayoutInflater
21+
import android.view.View
22+
import android.view.ViewGroup
23+
import androidx.lifecycle.lifecycleScope
24+
import im.vector.app.R
25+
import im.vector.app.core.extensions.associateContentStateWith
26+
import im.vector.app.core.extensions.autofillPhoneNumber
27+
import im.vector.app.core.extensions.content
28+
import im.vector.app.core.extensions.editText
29+
import im.vector.app.core.extensions.setOnImeDoneListener
30+
import im.vector.app.databinding.FragmentFtuePhoneInputBinding
31+
import im.vector.app.features.onboarding.OnboardingAction
32+
import im.vector.app.features.onboarding.RegisterAction
33+
import kotlinx.coroutines.flow.launchIn
34+
import kotlinx.coroutines.flow.onEach
35+
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
36+
import reactivecircus.flowbinding.android.widget.textChanges
37+
import javax.inject.Inject
38+
39+
class FtueAuthPhoneEntryFragment @Inject constructor(
40+
private val phoneNumberParser: PhoneNumberParser
41+
) : AbstractFtueAuthFragment<FragmentFtuePhoneInputBinding>() {
42+
43+
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtuePhoneInputBinding {
44+
return FragmentFtuePhoneInputBinding.inflate(inflater, container, false)
45+
}
46+
47+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48+
super.onViewCreated(view, savedInstanceState)
49+
setupViews()
50+
}
51+
52+
private fun setupViews() {
53+
views.phoneEntryInput.associateContentStateWith(button = views.phoneEntrySubmit)
54+
views.phoneEntryInput.setOnImeDoneListener { updatePhoneNumber() }
55+
views.phoneEntrySubmit.debouncedClicks { updatePhoneNumber() }
56+
57+
views.phoneEntryInput.editText().textChanges()
58+
.onEach {
59+
views.phoneEntryInput.error = null
60+
views.phoneEntrySubmit.isEnabled = it.isNotBlank()
61+
}
62+
.launchIn(viewLifecycleOwner.lifecycleScope)
63+
64+
views.phoneEntryInput.autofillPhoneNumber()
65+
}
66+
67+
private fun updatePhoneNumber() {
68+
val number = views.phoneEntryInput.content()
69+
70+
when (val result = phoneNumberParser.parseInternationalNumber(number)) {
71+
PhoneNumberParser.Result.ErrorInvalidNumber -> views.phoneEntryInput.error = getString(R.string.login_msisdn_error_other)
72+
PhoneNumberParser.Result.ErrorMissingInternationalCode -> views.phoneEntryInput.error = getString(R.string.login_msisdn_error_not_international)
73+
is PhoneNumberParser.Result.Success -> {
74+
val (countryCode, phoneNumber) = result
75+
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Msisdn(phoneNumber, countryCode))))
76+
}
77+
}
78+
}
79+
80+
override fun onError(throwable: Throwable) {
81+
views.phoneEntryInput.error = errorFormatter.toHumanReadable(throwable)
82+
}
83+
84+
override fun resetViewModel() {
85+
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
86+
}
87+
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,21 @@ class FtueAuthVariant(
388388
when (stage) {
389389
is Stage.ReCaptcha -> onCaptcha(stage)
390390
is Stage.Email -> onEmail(stage)
391-
is Stage.Msisdn -> addRegistrationStageFragmentToBackstack(
391+
is Stage.Msisdn -> onMsisdn(stage)
392+
is Stage.Terms -> onTerms(stage)
393+
else -> Unit // Should not happen
394+
}
395+
}
396+
397+
private fun onMsisdn(stage: Stage) {
398+
when {
399+
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
400+
FtueAuthPhoneEntryFragment::class.java
401+
)
402+
else -> addRegistrationStageFragmentToBackstack(
392403
FtueAuthGenericTextInputFormFragment::class.java,
393404
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
394405
)
395-
is Stage.Terms -> onTerms(stage)
396-
else -> Unit // Should not happen
397406
}
398407
}
399408

0 commit comments

Comments
 (0)