Merge pull request #5868 from vector-im/feature/adm/ftue-email-verification
[FTUE] - Email input and verification
This commit is contained in:
commit
98999c754f
|
@ -0,0 +1 @@
|
||||||
|
Adds email input and verification screens to the new FTUE onboarding flow
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<gradient
|
||||||
|
android:angle="@integer/rtl_mirror_flip"
|
||||||
|
android:endColor="#55DFD1FF"
|
||||||
|
android:startColor="#55A5F2E0" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<gradient
|
||||||
|
android:angle="90"
|
||||||
|
android:endColor="@android:color/transparent"
|
||||||
|
android:startColor="?android:colorBackground" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
|
@ -101,8 +101,10 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment
|
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.FtueAuthGenericTextInputFormFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment
|
||||||
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyWaitForEmailFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthPersonalizationCompleteFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthPersonalizationCompleteFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment
|
||||||
|
@ -474,6 +476,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(FtueAuthWaitForEmailFragment::class)
|
@FragmentKey(FtueAuthWaitForEmailFragment::class)
|
||||||
fun bindFtueAuthWaitForEmailFragment(fragment: FtueAuthWaitForEmailFragment): Fragment
|
fun bindFtueAuthWaitForEmailFragment(fragment: FtueAuthWaitForEmailFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(FtueAuthLegacyWaitForEmailFragment::class)
|
||||||
|
fun bindFtueAuthLegacyWaitForEmailFragment(fragment: FtueAuthLegacyWaitForEmailFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(FtueAuthWebFragment::class)
|
@FragmentKey(FtueAuthWebFragment::class)
|
||||||
|
@ -494,6 +501,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(FtueAuthAccountCreatedFragment::class)
|
@FragmentKey(FtueAuthAccountCreatedFragment::class)
|
||||||
fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment
|
fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(FtueAuthEmailEntryFragment::class)
|
||||||
|
fun bindFtueAuthEmailEntryFragment(fragment: FtueAuthEmailEntryFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(FtueAuthChooseDisplayNameFragment::class)
|
@FragmentKey(FtueAuthChooseDisplayNameFragment::class)
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.core.extensions
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property delegate for automatically cancelling the current job when setting a new value.
|
||||||
|
*/
|
||||||
|
fun cancelCurrentOnSet(): ReadWriteProperty<Any?, Job?> = object : ReadWriteProperty<Any?, Job?> {
|
||||||
|
private var currentJob: Job? = null
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): Job? = currentJob
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Job?) {
|
||||||
|
currentJob?.cancel()
|
||||||
|
currentJob = value
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,11 @@
|
||||||
|
|
||||||
package im.vector.app.core.extensions
|
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 com.google.android.material.textfield.TextInputLayout
|
||||||
|
import im.vector.app.core.platform.SimpleTextWatcher
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import reactivecircus.flowbinding.android.widget.textChanges
|
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.hasContentFlow(mapper: (CharSequence) -> CharSequence = { it }) = editText().textChanges().map { mapper(it).isNotEmpty() }
|
||||||
|
|
||||||
fun TextInputLayout.content() = editText().text.toString()
|
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.setOnImeDoneListener(action: () -> Unit) {
|
||||||
|
editText().setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
when (actionId) {
|
||||||
|
EditorInfo.IME_ACTION_DONE -> {
|
||||||
|
action()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.text.style.ForegroundColorSpan
|
||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import me.gujun.android.span.Span
|
import me.gujun.android.span.Span
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
|
||||||
fun Spannable.styleMatchingText(match: String, typeFace: Int): Spannable {
|
fun Spannable.styleMatchingText(match: String, typeFace: Int): Spannable {
|
||||||
if (match.isEmpty()) return this
|
if (match.isEmpty()) return this
|
||||||
|
@ -56,3 +57,17 @@ fun Span.bullet(text: CharSequence = "",
|
||||||
build()
|
build()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.colorTerminatingFullStop(@ColorInt color: Int): CharSequence {
|
||||||
|
val fullStop = "."
|
||||||
|
return if (endsWith(fullStop)) {
|
||||||
|
span {
|
||||||
|
+this@colorTerminatingFullStop.removeSuffix(fullStop)
|
||||||
|
span(fullStop) {
|
||||||
|
textColor = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.extensions.cancelCurrentOnSet
|
||||||
import im.vector.app.core.extensions.configureAndStart
|
import im.vector.app.core.extensions.configureAndStart
|
||||||
import im.vector.app.core.extensions.vectorStore
|
import im.vector.app.core.extensions.vectorStore
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
@ -50,7 +51,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.data.HomeServerConnectionConfig
|
||||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
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.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.RegistrationWizard
|
||||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -125,12 +125,8 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private var loginConfig: LoginConfig? = null
|
private var loginConfig: LoginConfig? = null
|
||||||
|
|
||||||
private var currentJob: Job? = null
|
private var emailVerificationPollingJob: Job? by cancelCurrentOnSet()
|
||||||
set(value) {
|
private var currentJob: Job? by cancelCurrentOnSet()
|
||||||
// Cancel any previous Job
|
|
||||||
field?.cancel()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handle(action: OnboardingAction) {
|
override fun handle(action: OnboardingAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
|
@ -257,13 +253,19 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRegisterAction(action: RegisterAction, onNextRegistrationStepAction: (FlowResult) -> Unit) {
|
private fun handleRegisterAction(action: RegisterAction, onNextRegistrationStepAction: (FlowResult) -> Unit) {
|
||||||
currentJob = viewModelScope.launch {
|
val job = viewModelScope.launch {
|
||||||
if (action.hasLoadingState()) {
|
if (action.hasLoadingState()) {
|
||||||
setState { copy(isLoading = true) }
|
setState { copy(isLoading = true) }
|
||||||
}
|
}
|
||||||
internalRegisterAction(action, onNextRegistrationStepAction)
|
internalRegisterAction(action, onNextRegistrationStepAction)
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow email verification polling to coexist with other jobs
|
||||||
|
when (action) {
|
||||||
|
is RegisterAction.CheckIfEmailHasBeenValidated -> emailVerificationPollingJob = job
|
||||||
|
else -> currentJob = job
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun internalRegisterAction(action: RegisterAction, onNextRegistrationStepAction: (FlowResult) -> Unit) {
|
private suspend fun internalRegisterAction(action: RegisterAction, onNextRegistrationStepAction: (FlowResult) -> Unit) {
|
||||||
|
@ -275,8 +277,10 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
else -> when (it) {
|
else -> when (it) {
|
||||||
is RegistrationResult.Success -> onSessionCreated(it.session, isAccountCreated = true)
|
is RegistrationResult.Complete -> onSessionCreated(it.session, isAccountCreated = true)
|
||||||
is RegistrationResult.FlowResponse -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -307,6 +311,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
private fun handleResetAction(action: OnboardingAction.ResetAction) {
|
private fun handleResetAction(action: OnboardingAction.ResetAction) {
|
||||||
// Cancel any request
|
// Cancel any request
|
||||||
currentJob = null
|
currentJob = null
|
||||||
|
emailVerificationPollingJob = null
|
||||||
|
|
||||||
when (action) {
|
when (action) {
|
||||||
OnboardingAction.ResetHomeServerType -> {
|
OnboardingAction.ResetHomeServerType -> {
|
||||||
|
@ -790,7 +795,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelWaitForEmailValidation() {
|
private fun cancelWaitForEmailValidation() {
|
||||||
currentJob = null
|
emailVerificationPollingJob = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,26 +16,80 @@
|
||||||
|
|
||||||
package im.vector.app.features.onboarding
|
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.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.auth.registration.RegistrationWizard
|
||||||
|
import org.matrix.android.sdk.api.failure.is401
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationResult as MatrixRegistrationResult
|
||||||
|
|
||||||
class RegistrationActionHandler @Inject constructor() {
|
class RegistrationActionHandler @Inject constructor() {
|
||||||
|
|
||||||
suspend fun handleRegisterAction(registrationWizard: RegistrationWizard, action: RegisterAction): RegistrationResult {
|
suspend fun handleRegisterAction(registrationWizard: RegistrationWizard, action: RegisterAction): RegistrationResult {
|
||||||
return when (action) {
|
return when (action) {
|
||||||
RegisterAction.StartRegistration -> registrationWizard.getRegistrationFlow()
|
RegisterAction.StartRegistration -> resultOf { registrationWizard.getRegistrationFlow() }
|
||||||
is RegisterAction.CaptchaDone -> registrationWizard.performReCaptcha(action.captchaResponse)
|
is RegisterAction.CaptchaDone -> resultOf { registrationWizard.performReCaptcha(action.captchaResponse) }
|
||||||
is RegisterAction.AcceptTerms -> registrationWizard.acceptTerms()
|
is RegisterAction.AcceptTerms -> resultOf { registrationWizard.acceptTerms() }
|
||||||
is RegisterAction.RegisterDummy -> registrationWizard.dummy()
|
is RegisterAction.RegisterDummy -> resultOf { registrationWizard.dummy() }
|
||||||
is RegisterAction.AddThreePid -> registrationWizard.addThreePid(action.threePid)
|
is RegisterAction.AddThreePid -> handleAddThreePid(registrationWizard, action)
|
||||||
is RegisterAction.SendAgainThreePid -> registrationWizard.sendAgainThreePid()
|
is RegisterAction.SendAgainThreePid -> resultOf { registrationWizard.sendAgainThreePid() }
|
||||||
is RegisterAction.ValidateThreePid -> registrationWizard.handleValidateThreePid(action.code)
|
is RegisterAction.ValidateThreePid -> resultOf { registrationWizard.handleValidateThreePid(action.code) }
|
||||||
is RegisterAction.CheckIfEmailHasBeenValidated -> registrationWizard.checkIfEmailHasBeenValidated(action.delayMillis)
|
is RegisterAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailIsValidated(registrationWizard, action.delayMillis)
|
||||||
is RegisterAction.CreateAccount -> registrationWizard.createAccount(action.username, action.password, action.initialDeviceName)
|
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 = { it.toRegistrationResult() },
|
||||||
|
onFailure = {
|
||||||
|
when {
|
||||||
|
action.threePid is RegisterThreePid.Email && it.is401() -> RegistrationResult.SendEmailSuccess(action.threePid.email)
|
||||||
|
else -> RegistrationResult.Error(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private tailrec suspend fun handleCheckIfEmailIsValidated(registrationWizard: RegistrationWizard, delayMillis: Long): RegistrationResult {
|
||||||
|
return runCatching { registrationWizard.checkIfEmailHasBeenValidated(delayMillis) }.fold(
|
||||||
|
onSuccess = { it.toRegistrationResult() },
|
||||||
|
onFailure = {
|
||||||
|
when {
|
||||||
|
it.is401() -> null // recursively continue to check with a delay
|
||||||
|
else -> RegistrationResult.Error(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) ?: handleCheckIfEmailIsValidated(registrationWizard, 10_000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun resultOf(block: () -> MatrixRegistrationResult): RegistrationResult {
|
||||||
|
return runCatching { block() }.fold(
|
||||||
|
onSuccess = { it.toRegistrationResult() },
|
||||||
|
onFailure = { RegistrationResult.Error(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MatrixRegistrationResult.toRegistrationResult() = when (this) {
|
||||||
|
is FlowResponse -> RegistrationResult.NextStep(flowResult)
|
||||||
|
is Success -> RegistrationResult.Complete(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
sealed interface RegisterAction {
|
||||||
|
@ -56,7 +110,6 @@ sealed interface RegisterAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RegisterAction.ignoresResult() = when (this) {
|
fun RegisterAction.ignoresResult() = when (this) {
|
||||||
is RegisterAction.AddThreePid -> true
|
|
||||||
is RegisterAction.SendAgainThreePid -> true
|
is RegisterAction.SendAgainThreePid -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
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.core.platform.SimpleTextWatcher
|
||||||
import im.vector.app.databinding.FragmentFtueDisplayNameBinding
|
import im.vector.app.databinding.FragmentFtueDisplayNameBinding
|
||||||
import im.vector.app.features.onboarding.OnboardingAction
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
|
@ -69,7 +69,7 @@ class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuth
|
||||||
|
|
||||||
override fun updateWithState(state: OnboardingViewState) {
|
override fun updateWithState(state: OnboardingViewState) {
|
||||||
views.displayNameInput.editText?.setText(state.personalizationState.displayName)
|
views.displayNameInput.editText?.setText(state.personalizationState.displayName)
|
||||||
views.displayNameSubmit.isEnabled = views.displayNameInput.hasContentEmpty()
|
views.displayNameSubmit.isEnabled = views.displayNameInput.hasContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
|
@ -81,5 +81,3 @@ class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuth
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TextInputLayout.hasContentEmpty() = !editText?.text.isNullOrEmpty()
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFt
|
||||||
views.chooseServerSubmit.debouncedClicks { updateServerUrl() }
|
views.chooseServerSubmit.debouncedClicks { updateServerUrl() }
|
||||||
views.chooseServerInput.editText().textChanges()
|
views.chooseServerInput.editText().textChanges()
|
||||||
.onEach { views.chooseServerInput.error = null }
|
.onEach { views.chooseServerInput.error = null }
|
||||||
.launchIn(lifecycleScope)
|
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateServerUrl() {
|
private fun updateServerUrl() {
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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.isEmail
|
||||||
|
import im.vector.app.core.extensions.setOnImeDoneListener
|
||||||
|
import im.vector.app.databinding.FragmentFtueEmailInputBinding
|
||||||
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
|
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 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.setOnImeDoneListener { updateEmail() }
|
||||||
|
views.emailEntrySubmit.debouncedClicks { updateEmail() }
|
||||||
|
|
||||||
|
views.emailEntryInput.editText().textChanges()
|
||||||
|
.onEach {
|
||||||
|
views.emailEntryInput.error = null
|
||||||
|
views.emailEntrySubmit.isEnabled = it.isEmail()
|
||||||
|
}
|
||||||
|
.launchIn(viewLifecycleOwner.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 resetViewModel() {
|
||||||
|
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
|
||||||
|
}
|
||||||
|
}
|
|
@ -223,13 +223,8 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA
|
||||||
override fun onError(throwable: Throwable) {
|
override fun onError(throwable: Throwable) {
|
||||||
when (params.mode) {
|
when (params.mode) {
|
||||||
TextInputFormFragmentMode.SetEmail -> {
|
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 -> {
|
TextInputFormFragmentMode.SetMsisdn -> {
|
||||||
if (throwable.is401()) {
|
if (throwable.is401()) {
|
||||||
// This is normal use case, we go to the enter code screen
|
// This is normal use case, we go to the enter code screen
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* 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 com.airbnb.mvrx.args
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.databinding.FragmentLoginWaitForEmailBinding
|
||||||
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
|
import im.vector.app.features.onboarding.RegisterAction
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In this screen, the user is asked to check their emails.
|
||||||
|
*/
|
||||||
|
class FtueAuthLegacyWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragment<FragmentLoginWaitForEmailBinding>() {
|
||||||
|
|
||||||
|
private val params: FtueAuthWaitForEmailFragmentArgument by args()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWaitForEmailBinding {
|
||||||
|
return FragmentLoginWaitForEmailBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupUi()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
viewModel.handle(OnboardingAction.StopEmailValidationCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUi() {
|
||||||
|
views.loginWaitForEmailNotice.text = getString(R.string.login_wait_for_email_notice, params.email)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resetViewModel() {
|
||||||
|
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
@ -90,7 +92,7 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
|
||||||
|
|
||||||
private fun ViewPager2.registerAutomaticUntilInteractionTransitions() {
|
private fun ViewPager2.registerAutomaticUntilInteractionTransitions() {
|
||||||
var scheduledTransition: Job? = null
|
var scheduledTransition: Job? = null
|
||||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
val pageChangingCallback = object : ViewPager2.OnPageChangeCallback() {
|
||||||
private var hasUserManuallyInteractedWithCarousel: Boolean = false
|
private var hasUserManuallyInteractedWithCarousel: Boolean = false
|
||||||
|
|
||||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||||
|
@ -104,12 +106,21 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
|
||||||
scheduledTransition = scheduleCarouselTransition()
|
scheduledTransition = scheduleCarouselTransition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||||
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
|
registerOnPageChangeCallback(pageChangingCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
unregisterOnPageChangeCallback(pageChangingCallback)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ViewPager2.scheduleCarouselTransition(): Job {
|
private fun ViewPager2.scheduleCarouselTransition(): Job {
|
||||||
val itemCount = adapter?.itemCount ?: throw IllegalStateException("An adapter must be set")
|
val itemCount = adapter?.itemCount ?: throw IllegalStateException("An adapter must be set")
|
||||||
return lifecycleScope.launch {
|
return viewLifecycleOwner.lifecycleScope.launch {
|
||||||
delay(CAROUSEL_ROTATION_DELAY_MS)
|
delay(CAROUSEL_ROTATION_DELAY_MS)
|
||||||
setCurrentItem(currentItem.incrementByOneAndWrap(max = itemCount - 1), duration = CAROUSEL_TRANSITION_TIME_MS)
|
setCurrentItem(currentItem.incrementByOneAndWrap(max = itemCount - 1), duration = CAROUSEL_TRANSITION_TIME_MS)
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,12 +192,7 @@ class FtueAuthVariant(
|
||||||
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
is OnboardingViewEvents.OnSendEmailSuccess -> {
|
is OnboardingViewEvents.OnSendEmailSuccess -> {
|
||||||
// Pop the enter email Fragment
|
openWaitForEmailVerification(viewEvents.email)
|
||||||
supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
|
||||||
addRegistrationStageFragmentToBackstack(
|
|
||||||
FtueAuthWaitForEmailFragment::class.java,
|
|
||||||
FtueAuthWaitForEmailFragmentArgument(viewEvents.email),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
is OnboardingViewEvents.OnSendMsisdnSuccess -> {
|
is OnboardingViewEvents.OnSendMsisdnSuccess -> {
|
||||||
// Pop the enter Msisdn Fragment
|
// Pop the enter Msisdn Fragment
|
||||||
|
@ -393,10 +388,7 @@ class FtueAuthVariant(
|
||||||
|
|
||||||
when (stage) {
|
when (stage) {
|
||||||
is Stage.ReCaptcha -> onCaptcha(stage)
|
is Stage.ReCaptcha -> onCaptcha(stage)
|
||||||
is Stage.Email -> addRegistrationStageFragmentToBackstack(
|
is Stage.Email -> onEmail(stage)
|
||||||
FtueAuthGenericTextInputFormFragment::class.java,
|
|
||||||
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
|
|
||||||
)
|
|
||||||
is Stage.Msisdn -> addRegistrationStageFragmentToBackstack(
|
is Stage.Msisdn -> addRegistrationStageFragmentToBackstack(
|
||||||
FtueAuthGenericTextInputFormFragment::class.java,
|
FtueAuthGenericTextInputFormFragment::class.java,
|
||||||
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
|
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
|
||||||
|
@ -406,6 +398,32 @@ 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 openWaitForEmailVerification(email: String) {
|
||||||
|
supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||||
|
when {
|
||||||
|
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
|
||||||
|
FtueAuthWaitForEmailFragment::class.java,
|
||||||
|
FtueAuthWaitForEmailFragmentArgument(email),
|
||||||
|
)
|
||||||
|
else -> addRegistrationStageFragmentToBackstack(
|
||||||
|
FtueAuthLegacyWaitForEmailFragment::class.java,
|
||||||
|
FtueAuthWaitForEmailFragmentArgument(email),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onTerms(stage: Stage.Terms) {
|
private fun onTerms(stage: Stage.Terms) {
|
||||||
when {
|
when {
|
||||||
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
|
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
|
||||||
|
|
|
@ -21,13 +21,16 @@ import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.databinding.FragmentLoginWaitForEmailBinding
|
import im.vector.app.core.utils.colorTerminatingFullStop
|
||||||
|
import im.vector.app.databinding.FragmentFtueWaitForEmailVerificationBinding
|
||||||
import im.vector.app.features.onboarding.OnboardingAction
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.RegisterAction
|
import im.vector.app.features.onboarding.RegisterAction
|
||||||
|
import im.vector.app.features.themes.ThemeProvider
|
||||||
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.failure.is401
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
@ -38,45 +41,57 @@ data class FtueAuthWaitForEmailFragmentArgument(
|
||||||
/**
|
/**
|
||||||
* In this screen, the user is asked to check their emails.
|
* In this screen, the user is asked to check their emails.
|
||||||
*/
|
*/
|
||||||
class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragment<FragmentLoginWaitForEmailBinding>() {
|
class FtueAuthWaitForEmailFragment @Inject constructor(
|
||||||
|
private val themeProvider: ThemeProvider
|
||||||
|
) : AbstractFtueAuthFragment<FragmentFtueWaitForEmailVerificationBinding>() {
|
||||||
|
|
||||||
private val params: FtueAuthWaitForEmailFragmentArgument by args()
|
private val params: FtueAuthWaitForEmailFragmentArgument by args()
|
||||||
|
private var inferHasLeftAndReturnedToScreen = false
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWaitForEmailBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueWaitForEmailVerificationBinding {
|
||||||
return FragmentLoginWaitForEmailBinding.inflate(inflater, container, false)
|
return FragmentFtueWaitForEmailVerificationBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
setupUi()
|
setupUi()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupUi() {
|
||||||
|
views.emailVerificationGradientContainer.setBackgroundResource(
|
||||||
|
when (themeProvider.isLightTheme()) {
|
||||||
|
true -> R.drawable.bg_waiting_for_email_verification
|
||||||
|
false -> R.drawable.bg_color_background
|
||||||
|
}
|
||||||
|
)
|
||||||
|
views.emailVerificationTitle.text = getString(R.string.ftue_auth_email_verification_title)
|
||||||
|
.colorTerminatingFullStop(ThemeUtils.getColor(requireContext(), R.attr.colorSecondary))
|
||||||
|
views.emailVerificationSubtitle.text = getString(R.string.ftue_auth_email_verification_subtitle, params.email)
|
||||||
|
views.emailVerificationResendEmail.debouncedClicks {
|
||||||
|
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.SendAgainThreePid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
showLoadingIfReturningToScreen()
|
||||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0)))
|
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showLoadingIfReturningToScreen() {
|
||||||
|
when (inferHasLeftAndReturnedToScreen) {
|
||||||
|
true -> views.emailVerificationWaiting.isVisible = true
|
||||||
|
false -> {
|
||||||
|
inferHasLeftAndReturnedToScreen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
||||||
viewModel.handle(OnboardingAction.StopEmailValidationCheck)
|
viewModel.handle(OnboardingAction.StopEmailValidationCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUi() {
|
|
||||||
views.loginWaitForEmailNotice.text = getString(R.string.login_wait_for_email_notice, params.email)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(throwable: Throwable) {
|
|
||||||
if (throwable.is401()) {
|
|
||||||
// Try again, with a delay
|
|
||||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(10_000)))
|
|
||||||
} else {
|
|
||||||
super.onError(throwable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
|
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,11 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.resources.LocaleProvider
|
import im.vector.app.core.resources.LocaleProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.resources.isEnglishSpeaking
|
import im.vector.app.core.resources.isEnglishSpeaking
|
||||||
|
import im.vector.app.core.utils.colorTerminatingFullStop
|
||||||
import im.vector.app.features.themes.ThemeProvider
|
import im.vector.app.features.themes.ThemeProvider
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
import me.gujun.android.span.span
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SplashCarouselStateFactory @Inject constructor(
|
class SplashCarouselStateFactory @Inject constructor(
|
||||||
|
@ -39,7 +39,7 @@ class SplashCarouselStateFactory @Inject constructor(
|
||||||
|
|
||||||
fun create(): SplashCarouselState {
|
fun create(): SplashCarouselState {
|
||||||
val lightTheme = themeProvider.isLightTheme()
|
val lightTheme = themeProvider.isLightTheme()
|
||||||
fun background(@DrawableRes lightDrawable: Int) = if (lightTheme) lightDrawable else R.drawable.bg_carousel_page_dark
|
fun background(@DrawableRes lightDrawable: Int) = if (lightTheme) lightDrawable else R.drawable.bg_color_background
|
||||||
fun hero(@DrawableRes lightDrawable: Int, @DrawableRes darkDrawable: Int) = if (lightTheme) lightDrawable else darkDrawable
|
fun hero(@DrawableRes lightDrawable: Int, @DrawableRes darkDrawable: Int) = if (lightTheme) lightDrawable else darkDrawable
|
||||||
return SplashCarouselState(
|
return SplashCarouselState(
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -79,18 +79,8 @@ class SplashCarouselStateFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Int.colorTerminatingFullStop(@AttrRes color: Int): EpoxyCharSequence {
|
private fun Int.colorTerminatingFullStop(@AttrRes color: Int): EpoxyCharSequence {
|
||||||
val string = stringProvider.getString(this)
|
return stringProvider.getString(this)
|
||||||
val fullStop = "."
|
.colorTerminatingFullStop(ThemeUtils.getColor(context, color))
|
||||||
val charSequence = if (string.endsWith(fullStop)) {
|
.toEpoxyCharSequence()
|
||||||
span {
|
|
||||||
+string.removeSuffix(fullStop)
|
|
||||||
span(fullStop) {
|
|
||||||
textColor = ThemeUtils.getColor(context, color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
string
|
|
||||||
}
|
|
||||||
return charSequence.toEpoxyCharSequence()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -0,0 +1,131 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,142 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/ftueAuthGutterStart"
|
||||||
|
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/ftueAuthGutterEnd"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/emailVerificationGradientContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintHeight_percent="0.60"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:background="@drawable/bg_waiting_for_email_verification" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/emailVerificationSpace1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/emailVerificationLogo"
|
||||||
|
app:layout_constraintHeight_percent="0.10"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="spread_inside" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/emailVerificationLogo"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:background="@drawable/circle"
|
||||||
|
android:backgroundTint="?colorSecondary"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/ic_email"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/emailVerificationSpace2"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_percent="0.12"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emailVerificationSpace1" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/emailVerificationSpace2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/emailVerificationTitle"
|
||||||
|
app:layout_constraintHeight_percent="0.05"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emailVerificationLogo" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/emailVerificationTitle"
|
||||||
|
style="@style/Widget.Vector.TextView.Title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:transitionName="loginTitleTransition"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/emailVerificationSubtitle"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emailVerificationSpace2"
|
||||||
|
tools:text="@string/ftue_auth_email_verification_title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/emailVerificationSubtitle"
|
||||||
|
style="@style/Widget.Vector.TextView.Subtitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/emailVerificationSpace4"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emailVerificationTitle"
|
||||||
|
tools:text="@string/ftue_auth_email_verification_subtitle" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/emailVerificationSpace4"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/emailVerificationResendEmail"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emailVerificationSubtitle" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/emailVerificationWaiting"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/emailVerificationSpace4"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/emailVerificationSpace4"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/emailVerificationFooter"
|
||||||
|
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_verification_footer"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/emailVerificationResendEmail"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emailVerificationSpace4" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/emailVerificationResendEmail"
|
||||||
|
style="@style/Widget.Vector.Button.Text.Login"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/element_background_light"
|
||||||
|
android:text="@string/ftue_auth_email_resend_email"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="?colorSecondary"
|
||||||
|
android:transitionName="loginSubmitTransition"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/emailVerificationSpace5"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emailVerificationFooter" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/emailVerificationSpace5"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintHeight_percent="0.05"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emailVerificationResendEmail" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -31,4 +31,13 @@
|
||||||
<string name="ftue_auth_terms_title">Privacy policy</string>
|
<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_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>
|
||||||
|
|
||||||
|
<string name="ftue_auth_email_verification_title">Check your email to verify.</string>
|
||||||
|
<!-- Note for translators, %s is the users email address -->
|
||||||
|
<string name="ftue_auth_email_verification_subtitle">To confirm your email address, tap the button in the email we just sent to %s</string>
|
||||||
|
<string name="ftue_auth_email_verification_footer">Did not receive an email?</string>
|
||||||
|
<string name="ftue_auth_email_resend_email">Resend email</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -46,8 +46,6 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
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.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.auth.registration.Stage
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||||
|
@ -57,10 +55,10 @@ private const val A_PICTURE_FILENAME = "a-picture.png"
|
||||||
private val AN_ERROR = RuntimeException("an error!")
|
private val AN_ERROR = RuntimeException("an error!")
|
||||||
private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration
|
private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration
|
||||||
private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L)
|
private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L)
|
||||||
private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.AddThreePid(RegisterThreePid.Email("an email"))
|
private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.SendAgainThreePid
|
||||||
private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true)
|
private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true)
|
||||||
private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList())
|
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 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 const val A_HOMESERVER_URL = "https://edited-homeserver.org"
|
||||||
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
|
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
|
||||||
|
@ -230,7 +228,7 @@ class OnboardingViewModelTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given register action ignores result, when handling action, then does nothing on success`() = runTest {
|
fun `given register action ignores result, when handling action, then does nothing on success`() = runTest {
|
||||||
val test = viewModel.test()
|
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))
|
viewModel.handle(OnboardingAction.PostRegisterAction(A_RESULT_IGNORED_REGISTER_ACTION))
|
||||||
|
|
||||||
|
@ -249,7 +247,7 @@ class OnboardingViewModelTest {
|
||||||
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
||||||
fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG)
|
fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG)
|
||||||
fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, SELECTED_HOMESERVER_STATE))
|
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())
|
fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString())
|
||||||
val test = viewModel.test()
|
val test = viewModel.test()
|
||||||
|
|
||||||
|
@ -291,7 +289,7 @@ class OnboardingViewModelTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest {
|
fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest {
|
||||||
fakeVectorFeatures.givenPersonalisationEnabled()
|
fakeVectorFeatures.givenPersonalisationEnabled()
|
||||||
givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession))
|
givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Complete(fakeSession))
|
||||||
givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
|
givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
|
||||||
val test = viewModel.test()
|
val test = viewModel.test()
|
||||||
|
|
||||||
|
@ -495,8 +493,8 @@ class OnboardingViewModelTest {
|
||||||
val flowResult = FlowResult(missingStages = missingStages, completedStages = emptyList())
|
val flowResult = FlowResult(missingStages = missingStages, completedStages = emptyList())
|
||||||
givenRegistrationResultsFor(
|
givenRegistrationResultsFor(
|
||||||
listOf(
|
listOf(
|
||||||
A_LOADABLE_REGISTER_ACTION to RegistrationResult.FlowResponse(flowResult),
|
A_LOADABLE_REGISTER_ACTION to RegistrationResult.NextStep(flowResult),
|
||||||
RegisterAction.RegisterDummy to RegistrationResult.Success(fakeSession)
|
RegisterAction.RegisterDummy to RegistrationResult.Complete(fakeSession)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
|
givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
|
||||||
|
|
|
@ -18,16 +18,19 @@ package im.vector.app.features.onboarding
|
||||||
|
|
||||||
import im.vector.app.test.fakes.FakeRegistrationWizard
|
import im.vector.app.test.fakes.FakeRegistrationWizard
|
||||||
import im.vector.app.test.fakes.FakeSession
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
import im.vector.app.test.fixtures.a401ServerError
|
||||||
import io.mockk.coVerifyAll
|
import io.mockk.coVerifyAll
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
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.RegistrationWizard
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationResult as SdkResult
|
||||||
|
|
||||||
|
private const val IGNORED_DELAY = 0L
|
||||||
|
private val AN_ERROR = RuntimeException()
|
||||||
private val A_SESSION = FakeSession()
|
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_USERNAME = "a username"
|
||||||
private const val A_PASSWORD = "a password"
|
private const val A_PASSWORD = "a password"
|
||||||
private const val AN_INITIAL_DEVICE_NAME = "a device name"
|
private const val AN_INITIAL_DEVICE_NAME = "a device name"
|
||||||
|
@ -38,6 +41,9 @@ private val A_PID_TO_REGISTER = RegisterThreePid.Email("an email")
|
||||||
|
|
||||||
class RegistrationActionHandlerTest {
|
class RegistrationActionHandlerTest {
|
||||||
|
|
||||||
|
private val fakeRegistrationWizard = FakeRegistrationWizard()
|
||||||
|
private val registrationActionHandler = RegistrationActionHandler()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when handling register action then delegates to wizard`() = runTest {
|
fun `when handling register action then delegates to wizard`() = runTest {
|
||||||
val cases = listOf(
|
val cases = listOf(
|
||||||
|
@ -57,9 +63,52 @@ class RegistrationActionHandlerTest {
|
||||||
cases.forEach { testSuccessfulActionDelegation(it) }
|
cases.forEach { testSuccessfulActionDelegation(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given adding an email ThreePid fails with 401, when handling register action, then infer EmailSuccess`() = runTest {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given email verification errors with 401 then fatal error, when checking email validation, then continues to poll until non 401 error`() = runTest {
|
||||||
|
val errorsToThrow = listOf(
|
||||||
|
a401ServerError(),
|
||||||
|
a401ServerError(),
|
||||||
|
a401ServerError(),
|
||||||
|
AN_ERROR
|
||||||
|
)
|
||||||
|
fakeRegistrationWizard.givenCheckIfEmailHasBeenValidatedErrors(errorsToThrow)
|
||||||
|
|
||||||
|
val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, RegisterAction.CheckIfEmailHasBeenValidated(IGNORED_DELAY))
|
||||||
|
|
||||||
|
fakeRegistrationWizard.verifyCheckedEmailedVerification(times = errorsToThrow.size)
|
||||||
|
result shouldBeEqualTo RegistrationResult.Error(AN_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given email verification errors with 401 and succeeds, when checking email validation, then continues to poll until success`() = runTest {
|
||||||
|
val errorsToThrow = listOf(
|
||||||
|
a401ServerError(),
|
||||||
|
a401ServerError(),
|
||||||
|
a401ServerError()
|
||||||
|
)
|
||||||
|
fakeRegistrationWizard.givenCheckIfEmailHasBeenValidatedErrors(errorsToThrow, finally = SdkResult.Success(A_SESSION))
|
||||||
|
|
||||||
|
val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, RegisterAction.CheckIfEmailHasBeenValidated(IGNORED_DELAY))
|
||||||
|
|
||||||
|
fakeRegistrationWizard.verifyCheckedEmailedVerification(times = errorsToThrow.size + 1)
|
||||||
|
result shouldBeEqualTo RegistrationResult.Complete(A_SESSION)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun testSuccessfulActionDelegation(case: Case) {
|
private suspend fun testSuccessfulActionDelegation(case: Case) {
|
||||||
val registrationActionHandler = RegistrationActionHandler()
|
|
||||||
val fakeRegistrationWizard = FakeRegistrationWizard()
|
val fakeRegistrationWizard = FakeRegistrationWizard()
|
||||||
|
val registrationActionHandler = RegistrationActionHandler()
|
||||||
fakeRegistrationWizard.givenSuccessFor(result = A_SESSION, case.expect)
|
fakeRegistrationWizard.givenSuccessFor(result = A_SESSION, case.expect)
|
||||||
|
|
||||||
val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, case.action)
|
val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, case.action)
|
||||||
|
@ -69,6 +118,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.RegisterAction
|
||||||
import im.vector.app.features.onboarding.RegistrationActionHandler
|
import im.vector.app.features.onboarding.RegistrationActionHandler
|
||||||
|
import im.vector.app.features.onboarding.RegistrationResult
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
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.RegistrationWizard
|
||||||
|
|
||||||
class FakeRegisterActionHandler {
|
class FakeRegisterActionHandler {
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
package im.vector.app.test.fakes
|
package im.vector.app.test.fakes
|
||||||
|
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
import io.mockk.mockk
|
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.RegistrationResult
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -27,4 +29,21 @@ class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) {
|
||||||
fun givenSuccessFor(result: Session, expect: suspend RegistrationWizard.() -> RegistrationResult) {
|
fun givenSuccessFor(result: Session, expect: suspend RegistrationWizard.() -> RegistrationResult) {
|
||||||
coEvery { expect(this@FakeRegistrationWizard) } returns RegistrationResult.Success(result)
|
coEvery { expect(this@FakeRegistrationWizard) } returns RegistrationResult.Success(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenAddEmailThreePidErrors(cause: Throwable, email: String) {
|
||||||
|
coEvery { addThreePid(RegisterThreePid.Email(email)) } throws cause
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenCheckIfEmailHasBeenValidatedErrors(errors: List<Throwable>, finally: RegistrationResult? = null) {
|
||||||
|
var index = 0
|
||||||
|
coEvery { checkIfEmailHasBeenValidated(any()) } answers {
|
||||||
|
val current = index
|
||||||
|
index++
|
||||||
|
errors.getOrNull(current)?.let { throw it } ?: finally ?: throw RuntimeException("Developer error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyCheckedEmailedVerification(times: Int) {
|
||||||
|
coVerify(exactly = times) { checkIfEmailHasBeenValidated(any()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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