-
Notifications
You must be signed in to change notification settings - Fork 741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FTUE] - Email input and verification #5868
Changes from all commits
d4a5b71
074e5bc
b2d8163
02b6916
4964c9f
817d692
eb4d31e
8a53eaf
9cc6467
350643c
8e7ae5e
735adf0
c0efd9f
8136f57
a4b5d18
c4834a4
c414f80
80b6b77
641c06f
4dc8d23
0979d56
bc5ebb2
47635aa
9fddd09
4bcdaa3
2378643
c71f9c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Adds email input and verification screens to the new FTUE onboarding flow |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ import android.text.style.ForegroundColorSpan | |
import android.text.style.StyleSpan | ||
import androidx.annotation.ColorInt | ||
import me.gujun.android.span.Span | ||
import me.gujun.android.span.span | ||
|
||
fun Spannable.styleMatchingText(match: String, typeFace: Int): Spannable { | ||
if (match.isEmpty()) return this | ||
|
@@ -56,3 +57,17 @@ fun Span.bullet(text: CharSequence = "", | |
build() | ||
}) | ||
} | ||
|
||
fun String.colorTerminatingFullStop(@ColorInt color: Int): CharSequence { | ||
val fullStop = "." | ||
return if (endsWith(fullStop)) { | ||
span { | ||
[email protected](fullStop) | ||
span(fullStop) { | ||
textColor = color | ||
} | ||
} | ||
} else { | ||
this | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,26 +16,80 @@ | |
|
||
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 MatrixRegistrationResult | ||
|
||
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 -> handleCheckIfEmailIsValidated(registrationWizard, 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 = { it.toRegistrationResult() }, | ||
onFailure = { | ||
when { | ||
action.threePid is RegisterThreePid.Email && it.is401() -> RegistrationResult.SendEmailSuccess(action.threePid.email) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the This logic feels like something that should be part of the SDK There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree! |
||
else -> RegistrationResult.Error(it) | ||
} | ||
} | ||
) | ||
} | ||
|
||
private tailrec suspend fun handleCheckIfEmailIsValidated(registrationWizard: RegistrationWizard, delayMillis: Long): RegistrationResult { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. uses |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. previously we were providing the verification polling through the entire |
||
} | ||
} | ||
|
||
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 { | ||
|
@@ -56,7 +110,6 @@ sealed interface RegisterAction { | |
} | ||
|
||
fun RegisterAction.ignoresResult() = when (this) { | ||
is RegisterAction.AddThreePid -> true | ||
is RegisterAction.SendAgainThreePid -> true | ||
else -> false | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,7 @@ import android.view.LayoutInflater | |
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 +69,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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, the previous name was quite weird :) |
||
} | ||
|
||
override fun resetViewModel() { | ||
|
@@ -81,5 +81,3 @@ class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuth | |
return true | ||
} | ||
} | ||
|
||
private fun TextInputLayout.hasContentEmpty() = !editText?.text.isNullOrEmpty() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe one day #5420 will be implemented...