mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
adding email input FTUE screen
- lifts the threepid email error handling to the RegistrationActionHandler rather than having the UI infer success from a 401
This commit is contained in:
parent
4094a66f3c
commit
d4a5b71a4d
@ -101,6 +101,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthEmailEntryFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
|
||||
@ -494,6 +495,11 @@ interface FragmentModule {
|
||||
@FragmentKey(FtueAuthAccountCreatedFragment::class)
|
||||
fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(FtueAuthEmailEntryFragment::class)
|
||||
fun bindFtueAuthEmailEntryFragment(fragment: FtueAuthEmailEntryFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(FtueAuthChooseDisplayNameFragment::class)
|
||||
|
@ -16,7 +16,11 @@
|
||||
|
||||
package im.vector.app.core.extensions
|
||||
|
||||
import android.text.Editable
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
import kotlinx.coroutines.flow.map
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
|
||||
@ -30,3 +34,26 @@ fun TextInputLayout.hasSurroundingSpaces() = editText().text.toString().let { it
|
||||
fun TextInputLayout.hasContentFlow(mapper: (CharSequence) -> CharSequence = { it }) = editText().textChanges().map { mapper(it).isNotEmpty() }
|
||||
|
||||
fun TextInputLayout.content() = editText().text.toString()
|
||||
|
||||
fun TextInputLayout.hasContent() = !editText?.text.isNullOrEmpty()
|
||||
|
||||
fun TextInputLayout.associateContentStateWith(button: View) {
|
||||
editText?.addTextChangedListener(object : SimpleTextWatcher() {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
val newContent = s.toString()
|
||||
button.isEnabled = newContent.isNotEmpty()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun TextInputLayout.setOnImeDone(action: () -> Unit) {
|
||||
editText?.setOnEditorActionListener { _, actionId, _ ->
|
||||
when (actionId) {
|
||||
EditorInfo.IME_ACTION_DONE -> {
|
||||
action()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,6 @@ import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
@ -275,8 +274,10 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
// do nothing
|
||||
}
|
||||
else -> when (it) {
|
||||
is RegistrationResult.Success -> onSessionCreated(it.session, isAccountCreated = true)
|
||||
is RegistrationResult.FlowResponse -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
|
||||
is RegistrationResult.Complete -> onSessionCreated(it.session, isAccountCreated = true)
|
||||
is RegistrationResult.NextStep -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
|
||||
is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email))
|
||||
is RegistrationResult.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -16,26 +16,73 @@
|
||||
|
||||
package im.vector.app.features.onboarding
|
||||
|
||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult.FlowResponse
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult.Success
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.failure.is401
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult as SdkRegistrationResult
|
||||
|
||||
class RegistrationActionHandler @Inject constructor() {
|
||||
|
||||
suspend fun handleRegisterAction(registrationWizard: RegistrationWizard, action: RegisterAction): RegistrationResult {
|
||||
return when (action) {
|
||||
RegisterAction.StartRegistration -> registrationWizard.getRegistrationFlow()
|
||||
is RegisterAction.CaptchaDone -> registrationWizard.performReCaptcha(action.captchaResponse)
|
||||
is RegisterAction.AcceptTerms -> registrationWizard.acceptTerms()
|
||||
is RegisterAction.RegisterDummy -> registrationWizard.dummy()
|
||||
is RegisterAction.AddThreePid -> registrationWizard.addThreePid(action.threePid)
|
||||
is RegisterAction.SendAgainThreePid -> registrationWizard.sendAgainThreePid()
|
||||
is RegisterAction.ValidateThreePid -> registrationWizard.handleValidateThreePid(action.code)
|
||||
is RegisterAction.CheckIfEmailHasBeenValidated -> registrationWizard.checkIfEmailHasBeenValidated(action.delayMillis)
|
||||
is RegisterAction.CreateAccount -> registrationWizard.createAccount(action.username, action.password, action.initialDeviceName)
|
||||
RegisterAction.StartRegistration -> resultOf { registrationWizard.getRegistrationFlow() }
|
||||
is RegisterAction.CaptchaDone -> resultOf { registrationWizard.performReCaptcha(action.captchaResponse) }
|
||||
is RegisterAction.AcceptTerms -> resultOf { registrationWizard.acceptTerms() }
|
||||
is RegisterAction.RegisterDummy -> resultOf { registrationWizard.dummy() }
|
||||
is RegisterAction.AddThreePid -> handleAddThreePid(registrationWizard, action)
|
||||
is RegisterAction.SendAgainThreePid -> resultOf { registrationWizard.sendAgainThreePid() }
|
||||
is RegisterAction.ValidateThreePid -> resultOf { registrationWizard.handleValidateThreePid(action.code) }
|
||||
is RegisterAction.CheckIfEmailHasBeenValidated -> resultOf { registrationWizard.checkIfEmailHasBeenValidated(action.delayMillis) }
|
||||
is RegisterAction.CreateAccount -> resultOf {
|
||||
registrationWizard.createAccount(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleAddThreePid(wizard: RegistrationWizard, action: RegisterAction.AddThreePid): RegistrationResult {
|
||||
return runCatching { wizard.addThreePid(action.threePid) }.fold(
|
||||
onSuccess = {
|
||||
when (it) {
|
||||
is Success -> RegistrationResult.Complete(it.session)
|
||||
is FlowResponse -> RegistrationResult.NextStep(it.flowResult)
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
when {
|
||||
action.threePid is RegisterThreePid.Email && it.is401() -> RegistrationResult.SendEmailSuccess(action.threePid.email)
|
||||
else -> RegistrationResult.Error(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun resultOf(block: () -> SdkRegistrationResult): RegistrationResult {
|
||||
return runCatching { block() }.fold(
|
||||
onSuccess = {
|
||||
when (it) {
|
||||
is FlowResponse -> RegistrationResult.NextStep(it.flowResult)
|
||||
is Success -> RegistrationResult.Complete(it.session)
|
||||
}
|
||||
},
|
||||
onFailure = { RegistrationResult.Error(it) }
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface RegistrationResult {
|
||||
data class Error(val cause: Throwable) : RegistrationResult
|
||||
data class Complete(val session: Session) : RegistrationResult
|
||||
data class NextStep(val flowResult: FlowResult) : RegistrationResult
|
||||
data class SendEmailSuccess(val email: String) : RegistrationResult
|
||||
}
|
||||
|
||||
sealed interface RegisterAction {
|
||||
@ -56,7 +103,6 @@ sealed interface RegisterAction {
|
||||
}
|
||||
|
||||
fun RegisterAction.ignoresResult() = when (this) {
|
||||
is RegisterAction.AddThreePid -> true
|
||||
is RegisterAction.SendAgainThreePid -> true
|
||||
else -> false
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.app.core.extensions.hasContent
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
import im.vector.app.databinding.FragmentFtueDisplayNameBinding
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
@ -69,7 +70,7 @@ class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuth
|
||||
|
||||
override fun updateWithState(state: OnboardingViewState) {
|
||||
views.displayNameInput.editText?.setText(state.personalizationState.displayName)
|
||||
views.displayNameSubmit.isEnabled = views.displayNameInput.hasContentEmpty()
|
||||
views.displayNameSubmit.isEnabled = views.displayNameInput.hasContent()
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
@ -81,5 +82,3 @@ class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuth
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun TextInputLayout.hasContentEmpty() = !editText?.text.isNullOrEmpty()
|
||||
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.onboarding.ftueauth
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.vector.app.core.extensions.associateContentStateWith
|
||||
import im.vector.app.core.extensions.content
|
||||
import im.vector.app.core.extensions.editText
|
||||
import im.vector.app.core.extensions.hasContent
|
||||
import im.vector.app.core.extensions.isEmail
|
||||
import im.vector.app.core.extensions.setOnImeDone
|
||||
import im.vector.app.databinding.FragmentFtueEmailInputBinding
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.failure.is401
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class FtueAuthEmailEntryFragment @Inject constructor() : AbstractFtueAuthFragment<FragmentFtueEmailInputBinding>() {
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueEmailInputBinding {
|
||||
return FragmentFtueEmailInputBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupViews()
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
views.emailEntryInput.associateContentStateWith(button = views.emailEntrySubmit)
|
||||
views.emailEntryInput.setOnImeDone { updateEmail() }
|
||||
views.emailEntrySubmit.debouncedClicks { updateEmail() }
|
||||
|
||||
views.emailEntryInput.editText().textChanges()
|
||||
.onEach {
|
||||
views.emailEntryInput.error = null
|
||||
views.emailEntrySubmit.isEnabled = it.isEmail()
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun updateEmail() {
|
||||
val email = views.emailEntryInput.content()
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Email(email))))
|
||||
}
|
||||
|
||||
override fun onError(throwable: Throwable) {
|
||||
views.emailEntryInput.error = errorFormatter.toHumanReadable(throwable)
|
||||
}
|
||||
|
||||
override fun updateWithState(state: OnboardingViewState) {
|
||||
views.emailEntrySubmit.isEnabled = views.emailEntryInput.content().isEmail()
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome))
|
||||
return true
|
||||
}
|
||||
}
|
@ -223,12 +223,7 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA
|
||||
override fun onError(throwable: Throwable) {
|
||||
when (params.mode) {
|
||||
TextInputFormFragmentMode.SetEmail -> {
|
||||
if (throwable.is401()) {
|
||||
// This is normal use case, we go to the mail waiting screen
|
||||
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(viewModel.currentThreePid ?: "")))
|
||||
} else {
|
||||
views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
|
||||
}
|
||||
views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
|
||||
}
|
||||
TextInputFormFragmentMode.SetMsisdn -> {
|
||||
if (throwable.is401()) {
|
||||
|
@ -393,10 +393,7 @@ class FtueAuthVariant(
|
||||
|
||||
when (stage) {
|
||||
is Stage.ReCaptcha -> onCaptcha(stage)
|
||||
is Stage.Email -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthGenericTextInputFormFragment::class.java,
|
||||
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
|
||||
)
|
||||
is Stage.Email -> onEmail(stage)
|
||||
is Stage.Msisdn -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthGenericTextInputFormFragment::class.java,
|
||||
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
|
||||
@ -406,6 +403,18 @@ class FtueAuthVariant(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEmail(stage: Stage) {
|
||||
when {
|
||||
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthEmailEntryFragment::class.java
|
||||
)
|
||||
else -> addRegistrationStageFragmentToBackstack(
|
||||
FtueAuthGenericTextInputFormFragment::class.java,
|
||||
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTerms(stage: Stage.Terms) {
|
||||
when {
|
||||
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
|
||||
|
10
vector/src/main/res/drawable/ic_email.xml
Normal file
10
vector/src/main/res/drawable/ic_email.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="71dp"
|
||||
android:height="70dp"
|
||||
android:viewportWidth="71"
|
||||
android:viewportHeight="70">
|
||||
<path
|
||||
android:pathData="M16.261,23.576L34.905,42.161C35.545,42.799 36.581,42.799 37.221,42.161L55.773,23.667C55.92,24.084 56,24.533 56,25V46C56,48.209 54.209,50 52,50H20C17.791,50 16,48.209 16,46V25C16,24.498 16.092,24.018 16.261,23.576ZM18.582,21.258C19.023,21.091 19.501,21 20,21H52C52.533,21 53.042,21.104 53.508,21.294L36.063,38.684L18.582,21.258Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
133
vector/src/main/res/layout/fragment_ftue_email_input.xml
Normal file
133
vector/src/main/res/layout/fragment_ftue_email_input.xml
Normal file
@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/LoginFormScrollView"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:colorBackground"
|
||||
android:fillViewport="true"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="0dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/emailEntryGutterStart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_start_percent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/emailEntryGutterEnd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/headerSpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/emailEntryHeaderIcon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/emailEntryHeaderIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/circle"
|
||||
android:backgroundTint="?colorSecondary"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_email"
|
||||
app:layout_constraintBottom_toTopOf="@id/emailEntryHeaderTitle"
|
||||
app:layout_constraintEnd_toEndOf="@id/emailEntryGutterEnd"
|
||||
app:layout_constraintHeight_percent="0.12"
|
||||
app:layout_constraintStart_toStartOf="@id/emailEntryGutterStart"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/palette_white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emailEntryHeaderTitle"
|
||||
style="@style/Widget.Vector.TextView.Title.Medium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/ftue_auth_email_title"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/emailEntryHeaderSubtitle"
|
||||
app:layout_constraintEnd_toEndOf="@id/emailEntryGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/emailEntryGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/emailEntryHeaderIcon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emailEntryHeaderSubtitle"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/ftue_auth_email_subtitle"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:layout_constraintBottom_toTopOf="@id/titleContentSpacing"
|
||||
app:layout_constraintEnd_toEndOf="@id/emailEntryGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/emailEntryGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/emailEntryHeaderTitle" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/titleContentSpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/emailEntryInput"
|
||||
app:layout_constraintHeight_percent="0.03"
|
||||
app:layout_constraintTop_toBottomOf="@id/emailEntryHeaderSubtitle" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/emailEntryInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/ftue_auth_email_entry_title"
|
||||
app:endIconMode="clear_text"
|
||||
app:layout_constraintEnd_toEndOf="@id/emailEntryGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/emailEntryGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleContentSpacing">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textEmailAddress"
|
||||
android:maxLines="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Space
|
||||
android:id="@+id/entrySpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/emailEntrySubmit"
|
||||
app:layout_constraintHeight_percent="0.03"
|
||||
app:layout_constraintTop_toBottomOf="@id/emailEntryInput"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/emailEntrySubmit"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_set_email_submit"
|
||||
android:textAllCaps="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/emailEntryGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/emailEntryGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/entrySpacing" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
@ -31,4 +31,8 @@
|
||||
<string name="ftue_auth_terms_title">Privacy policy</string>
|
||||
<string name="ftue_auth_terms_subtitle">Please read through T&C. You must accept in order to continue.</string>
|
||||
|
||||
<string name="ftue_auth_email_title">Enter your email address</string>
|
||||
<string name="ftue_auth_email_subtitle">This will help verify your account and enables password recovery.</string>
|
||||
<string name="ftue_auth_email_entry_title">Email Address</string>
|
||||
|
||||
</resources>
|
||||
|
@ -47,7 +47,6 @@ import org.junit.Test
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
@ -60,7 +59,7 @@ private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenV
|
||||
private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.AddThreePid(RegisterThreePid.Email("an email"))
|
||||
private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true)
|
||||
private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList())
|
||||
private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT)
|
||||
private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.NextStep(AN_IGNORED_FLOW_RESULT)
|
||||
private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name")
|
||||
private const val A_HOMESERVER_URL = "https://edited-homeserver.org"
|
||||
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
|
||||
@ -230,7 +229,7 @@ class OnboardingViewModelTest {
|
||||
@Test
|
||||
fun `given register action ignores result, when handling action, then does nothing on success`() = runTest {
|
||||
val test = viewModel.test()
|
||||
givenRegistrationResultFor(A_RESULT_IGNORED_REGISTER_ACTION, RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT))
|
||||
givenRegistrationResultFor(A_RESULT_IGNORED_REGISTER_ACTION, RegistrationResult.NextStep(AN_IGNORED_FLOW_RESULT))
|
||||
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(A_RESULT_IGNORED_REGISTER_ACTION))
|
||||
|
||||
@ -249,7 +248,7 @@ class OnboardingViewModelTest {
|
||||
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
||||
fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG)
|
||||
fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, SELECTED_HOMESERVER_STATE))
|
||||
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT))
|
||||
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationResult.NextStep(AN_IGNORED_FLOW_RESULT))
|
||||
fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString())
|
||||
val test = viewModel.test()
|
||||
|
||||
@ -291,7 +290,7 @@ class OnboardingViewModelTest {
|
||||
@Test
|
||||
fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest {
|
||||
fakeVectorFeatures.givenPersonalisationEnabled()
|
||||
givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession))
|
||||
givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Complete(fakeSession))
|
||||
givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
|
||||
val test = viewModel.test()
|
||||
|
||||
@ -495,8 +494,8 @@ class OnboardingViewModelTest {
|
||||
val flowResult = FlowResult(missingStages = missingStages, completedStages = emptyList())
|
||||
givenRegistrationResultsFor(
|
||||
listOf(
|
||||
A_LOADABLE_REGISTER_ACTION to RegistrationResult.FlowResponse(flowResult),
|
||||
RegisterAction.RegisterDummy to RegistrationResult.Success(fakeSession)
|
||||
A_LOADABLE_REGISTER_ACTION to RegistrationResult.NextStep(flowResult),
|
||||
RegisterAction.RegisterDummy to RegistrationResult.Complete(fakeSession)
|
||||
)
|
||||
)
|
||||
givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
|
||||
|
@ -18,16 +18,17 @@ package im.vector.app.features.onboarding
|
||||
|
||||
import im.vector.app.test.fakes.FakeRegistrationWizard
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fixtures.a401ServerError
|
||||
import io.mockk.coVerifyAll
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult as SdkResult
|
||||
|
||||
private val A_SESSION = FakeSession()
|
||||
private val AN_EXPECTED_RESULT = RegistrationResult.Success(A_SESSION)
|
||||
private val AN_EXPECTED_RESULT = RegistrationResult.Complete(A_SESSION)
|
||||
private const val A_USERNAME = "a username"
|
||||
private const val A_PASSWORD = "a password"
|
||||
private const val AN_INITIAL_DEVICE_NAME = "a device name"
|
||||
@ -57,6 +58,20 @@ class RegistrationActionHandlerTest {
|
||||
cases.forEach { testSuccessfulActionDelegation(it) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given adding an email ThreePid fails with 401, when handling register action, then infer EmailSuccess`() = runTest {
|
||||
val registrationActionHandler = RegistrationActionHandler()
|
||||
val fakeRegistrationWizard = FakeRegistrationWizard()
|
||||
fakeRegistrationWizard.givenAddEmailThreePidErrors(
|
||||
cause = a401ServerError(),
|
||||
email = A_PID_TO_REGISTER.email
|
||||
)
|
||||
|
||||
val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, RegisterAction.AddThreePid(A_PID_TO_REGISTER))
|
||||
|
||||
result shouldBeEqualTo RegistrationResult.SendEmailSuccess(A_PID_TO_REGISTER.email)
|
||||
}
|
||||
|
||||
private suspend fun testSuccessfulActionDelegation(case: Case) {
|
||||
val registrationActionHandler = RegistrationActionHandler()
|
||||
val fakeRegistrationWizard = FakeRegistrationWizard()
|
||||
@ -69,6 +84,6 @@ class RegistrationActionHandlerTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun case(action: RegisterAction, expect: suspend RegistrationWizard.() -> RegistrationResult) = Case(action, expect)
|
||||
private fun case(action: RegisterAction, expect: suspend RegistrationWizard.() -> SdkResult) = Case(action, expect)
|
||||
|
||||
private class Case(val action: RegisterAction, val expect: suspend RegistrationWizard.() -> RegistrationResult)
|
||||
private class Case(val action: RegisterAction, val expect: suspend RegistrationWizard.() -> SdkResult)
|
||||
|
@ -18,9 +18,9 @@ package im.vector.app.test.fakes
|
||||
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import im.vector.app.features.onboarding.RegistrationActionHandler
|
||||
import im.vector.app.features.onboarding.RegistrationResult
|
||||
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
|
||||
|
||||
class FakeRegisterActionHandler {
|
||||
|
@ -18,6 +18,7 @@ package im.vector.app.test.fakes
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
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
|
||||
@ -27,4 +28,8 @@ class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) {
|
||||
fun givenSuccessFor(result: Session, expect: suspend RegistrationWizard.() -> RegistrationResult) {
|
||||
coEvery { expect(this@FakeRegistrationWizard) } returns RegistrationResult.Success(result)
|
||||
}
|
||||
|
||||
fun givenAddEmailThreePidErrors(cause: Throwable, email: String) {
|
||||
coEvery { addThreePid(RegisterThreePid.Email(email)) } throws cause
|
||||
}
|
||||
}
|
||||
|
25
vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt
vendored
Normal file
25
vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.fixtures
|
||||
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
fun a401ServerError() = Failure.ServerError(
|
||||
MatrixError(MatrixError.M_UNAUTHORIZED, ""), HttpsURLConnection.HTTP_UNAUTHORIZED
|
||||
)
|
Loading…
Reference in New Issue
Block a user