Login screens: reset password WIP

This commit is contained in:
Benoit Marty 2019-11-21 18:52:09 +01:00
parent 810b226f21
commit 90027cc4d5
18 changed files with 388 additions and 54 deletions

View file

@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
/**
* This interface defines methods to authenticate to a matrix server.
* TODO Some methods has to be moved to and authenticationWizard, has it is done for registration
*/
interface Authenticator {
@ -78,4 +79,9 @@ interface Authenticator {
* Reset user password
*/
fun resetPassword(homeServerConnectionConfig: HomeServerConnectionConfig, email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Confirm the new password, once the user has check his email
*/
fun resetPasswordMailConfirmed(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<Unit>): Cancelable
}

View file

@ -20,6 +20,7 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.registration.*
import im.vector.matrix.android.internal.auth.signin.ResetPasswordMailConfirmed
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.*
@ -66,4 +67,16 @@ internal interface AuthAPI {
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
/**
* Ask the homeserver to reset the password associated with the provided email.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password/email/requestToken")
fun resetPassword(@Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse>
/**
* Ask the homeserver to reset the password with the provided new password once the email is validated.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password")
fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed): Call<Unit>
}

View file

@ -23,12 +23,18 @@ import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.NoOpCancellable
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask
import im.vector.matrix.android.internal.auth.signin.ResetPasswordMailConfirmed
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.network.RetrofitFactory
@ -39,8 +45,15 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import java.util.*
import javax.inject.Inject
// Container to store the data when a reset password is in the email validation step
internal data class ResetPasswordData(
val newPassword: String,
val addThreePidRegistrationResponse: AddThreePidRegistrationResponse
)
internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
@ -48,6 +61,10 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager
) : Authenticator {
private var clientSecret = UUID.randomUUID().toString()
private var sendAttempt = 0
private var resetPasswordData: ResetPasswordData? = null
override fun hasAuthenticatedSessions(): Boolean {
return sessionParamsStore.getLast() != null
@ -136,20 +153,58 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
override fun resetPassword(homeServerConnectionConfig: HomeServerConnectionConfig, email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val result = runCatching {
resetPasswordInternal(/*homeServerConnectionConfig, email, newPassword*/)
resetPasswordInternal(homeServerConnectionConfig, email, newPassword)
}
result.foldToCallback(callback)
}
return CancelableCoroutine(job)
}
private fun resetPasswordInternal(/*homeServerConnectionConfig: HomeServerConnectionConfig, email: String, newPassword: String*/) {
// TODO
error("Not implemented")
// val authAPI = buildAuthAPI(homeServerConnectionConfig)
// executeRequest<LoginFlowResponse> {
// apiCall = authAPI.getLoginFlows()
// }
private suspend fun resetPasswordInternal(homeServerConnectionConfig: HomeServerConnectionConfig, email: String, newPassword: String) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val param = RegisterAddThreePidTask.Params(
RegisterThreePid.Email(email),
clientSecret,
sendAttempt++
)
val result = executeRequest<AddThreePidRegistrationResponse> {
apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
}
resetPasswordData = ResetPasswordData(newPassword, result)
}
override fun resetPasswordMailConfirmed(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<Unit>): Cancelable {
val safeResetPasswordData = resetPasswordData ?: run {
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
return NoOpCancellable
}
val job = GlobalScope.launch(coroutineDispatchers.main) {
val result = runCatching {
resetPasswordMailConfirmedInternal(homeServerConnectionConfig, safeResetPasswordData)
}
result.foldToCallback(callback)
}
return CancelableCoroutine(job)
}
private suspend fun resetPasswordMailConfirmedInternal(homeServerConnectionConfig: HomeServerConnectionConfig, resetPasswordData: ResetPasswordData) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val param = ResetPasswordMailConfirmed.create(
clientSecret,
resetPasswordData.addThreePidRegistrationResponse.sid,
resetPasswordData.newPassword
)
executeRequest<Unit> {
apiCall = authAPI.resetPasswordMailConfirmed(param)
}
// Set to null?
// resetPasswordData = null
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {

View file

@ -28,8 +28,11 @@ internal data class AuthParams(
@Json(name = "type")
val type: String,
/**
* Note: session can be null for reset password request
*/
@Json(name = "session")
val session: String,
val session: String?,
/**
* parameter for "m.login.recaptcha" type
@ -72,6 +75,17 @@ internal data class AuthParams(
threePidCredentials = threePidCredentials
)
}
fun createForResetPassword(clientSecret: String, sid: String): AuthParams {
return AuthParams(
type = LoginFlowTypes.EMAIL_IDENTITY,
session = null,
threePidCredentials = ThreePidCredentials(
clientSecret = clientSecret,
sid = sid
)
)
}
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2014 OpenMarket Ltd
* Copyright 2017 Vector Creations Ltd
* Copyright 2018 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.matrix.android.internal.auth.signin
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.auth.registration.AuthParams
/**
* Class to pass parameters to reset the password once a email has been validated.
*/
@JsonClass(generateAdapter = true)
internal data class ResetPasswordMailConfirmed(
// authentication parameters
@Json(name = "auth")
val auth: AuthParams? = null,
// the new password
@Json(name = "new_password")
val newPassword: String? = null
) {
companion object {
fun create(clientSecret: String, sid: String, newPassword: String): ResetPasswordMailConfirmed {
return ResetPasswordMailConfirmed(
auth = AuthParams.createForResetPassword(clientSecret, sid),
newPassword = newPassword
)
}
}
}

View file

@ -130,6 +130,11 @@ interface FragmentModule {
@FragmentKey(LoginServerUrlFormFragment::class)
fun bindLoginServerUrlFormFragment(fragment: LoginServerUrlFormFragment): Fragment
@Binds
@IntoMap
@FragmentKey(LoginResetPasswordMailConfirmationFragment::class)
fun bindLoginResetPasswordMailConfirmationFragment(fragment: LoginResetPasswordMailConfirmationFragment): Fragment
@Binds
@IntoMap
@FragmentKey(LoginResetPasswordFragment::class)

View file

@ -67,6 +67,9 @@ class ErrorFormatter @Inject constructor(private val stringProvider: StringProvi
throwable.error.code == MatrixError.LIMIT_EXCEEDED -> {
stringProvider.getString(R.string.login_error_limit_exceeded)
}
throwable.error.code == MatrixError.THREEPID_NOT_FOUND -> {
stringProvider.getString(R.string.login_reset_password_error_not_found)
}
else -> {
throwable.error.message.takeIf { it.isNotEmpty() }
?: throwable.error.code.takeIf { it.isNotEmpty() }

View file

@ -28,6 +28,7 @@ sealed class LoginAction : VectorViewModelAction {
data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
data class InitWith(val loginConfig: LoginConfig) : LoginAction()
data class ResetPassword(val email: String, val newPassword: String) : LoginAction()
object ResetPasswordMailConfirmed : LoginAction()
// Register actions
open class RegisterAction : LoginAction()

View file

@ -72,29 +72,35 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
loginSharedActionViewModel = viewModelProvider.get(LoginSharedActionViewModel::class.java)
loginSharedActionViewModel.observe()
.subscribe {
when (it) {
is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java,
// Assigning to dummy make sure we do not forget a case
@Suppress("UNUSED_VARIABLE")
val dummy = when (it) {
is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java,
option = { ft ->
val view = findViewById<View?>(R.id.loginSplashLogo)
if (view != null) {
ft.addSharedElement(view, ViewCompat.getTransitionName(view) ?: "")
}
})
is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone()
is LoginNavigation.OnSignModeSelected -> onSignModeSelected()
is LoginNavigation.OnLoginFlowRetrieved -> onLoginFlowRetrieved()
is LoginNavigation.OnWebLoginError -> onWebLoginError(it)
is LoginNavigation.OnForgetPasswordClicked -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginResetPasswordFragment::class.java)
is LoginNavigation.OnResetPasswordSuccess -> {
is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone()
is LoginNavigation.OnSignModeSelected -> onSignModeSelected()
is LoginNavigation.OnLoginFlowRetrieved -> onLoginFlowRetrieved()
is LoginNavigation.OnWebLoginError -> onWebLoginError(it)
is LoginNavigation.OnForgetPasswordClicked -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginResetPasswordFragment::class.java)
is LoginNavigation.OnResetPasswordSendThreePidDone -> {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
addFragmentToBackstack(R.id.loginFragmentContainer, LoginResetPasswordMailConfirmationFragment::class.java)
}
is LoginNavigation.OnResetPasswordMailConfirmationSuccess -> {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
addFragmentToBackstack(R.id.loginFragmentContainer, LoginResetPasswordSuccessFragment::class.java)
}
is LoginNavigation.OnResetPasswordSuccessDone -> supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
is LoginNavigation.OnSendEmailSuccess -> addFragmentToBackstack(R.id.loginFragmentContainer,
is LoginNavigation.OnResetPasswordMailConfirmationSuccessDone -> supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
is LoginNavigation.OnSendEmailSuccess -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginWaitForEmailFragment::class.java,
LoginWaitForEmailFragmentArgument(it.email),
tag = FRAGMENT_REGISTRATION_STAGE_TAG)
is LoginNavigation.OnSendMsisdnSuccess -> addFragmentToBackstack(R.id.loginFragmentContainer,
is LoginNavigation.OnSendMsisdnSuccess -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginGenericTextInputFormFragment::class.java,
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, it.msisdn),
tag = FRAGMENT_REGISTRATION_STAGE_TAG)

View file

@ -25,8 +25,10 @@ sealed class LoginNavigation : VectorSharedAction {
object OnLoginFlowRetrieved : LoginNavigation()
object OnSignModeSelected : LoginNavigation()
object OnForgetPasswordClicked : LoginNavigation()
object OnResetPasswordSuccess : LoginNavigation()
object OnResetPasswordSuccessDone : LoginNavigation()
object OnResetPasswordSendThreePidDone : LoginNavigation()
object OnResetPasswordMailConfirmationSuccess : LoginNavigation()
object OnResetPasswordMailConfirmationSuccessDone : LoginNavigation()
data class OnSendEmailSuccess(val email: String) : LoginNavigation()
data class OnSendMsisdnSuccess(val msisdn: String) : LoginNavigation()

View file

@ -36,7 +36,6 @@ import io.reactivex.rxkotlin.subscribeBy
import kotlinx.android.synthetic.main.fragment_login.passwordField
import kotlinx.android.synthetic.main.fragment_login.passwordFieldTil
import kotlinx.android.synthetic.main.fragment_login.passwordReveal
import kotlinx.android.synthetic.main.fragment_login_generic_text_input_form.*
import kotlinx.android.synthetic.main.fragment_login_reset_password.*
import javax.inject.Inject
@ -49,6 +48,9 @@ class LoginResetPasswordFragment @Inject constructor(
private var passwordShown = false
// Show warning only once
private var showWarning = true
override fun getLayoutResId() = R.layout.fragment_login_reset_password
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -84,6 +86,23 @@ class LoginResetPasswordFragment @Inject constructor(
fun submit() {
cleanupUi()
if (showWarning) {
showWarning = false
// Display a warning as Riot-Web does first
AlertDialog.Builder(requireActivity())
.setTitle(R.string.login_reset_password_warning_title)
.setMessage(R.string.login_reset_password_warning_content)
.setPositiveButton(R.string.login_reset_password_warning_submit) { _, _ ->
doSubmit()
}
.setNegativeButton(R.string.cancel, null)
.show()
} else {
doSubmit()
}
}
private fun doSubmit() {
val email = resetPasswordEmail.text.toString()
val password = passwordField.text.toString()
@ -141,11 +160,10 @@ class LoginResetPasswordFragment @Inject constructor(
renderPasswordField()
}
is Fail -> {
resetPasswordEmailTil.error = " "
passwordFieldTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
}
is Success -> {
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordSuccess)
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordSendThreePidDone)
}
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright 2019 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.riotx.features.login
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import butterknife.OnClick
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.withState
import im.vector.riotx.R
import im.vector.riotx.core.error.ErrorFormatter
import kotlinx.android.synthetic.main.fragment_login_reset_password_success.*
import javax.inject.Inject
/**
* In this screen, the user is asked to check his email and to click on a button once it's done
*/
class LoginResetPasswordMailConfirmationFragment @Inject constructor(
private val errorFormatter: ErrorFormatter
) : AbstractLoginFragment() {
override fun getLayoutResId() = R.layout.fragment_login_reset_password_mail_confirmation
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUi()
}
private fun setupUi() {
resetPasswordSuccessNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, loginViewModel.resetPasswordEmail)
}
@OnClick(R.id.resetPasswordMailConfirmationSubmit)
fun submit() {
loginViewModel.handle(LoginAction.ResetPasswordMailConfirmed)
}
override fun onRegistrationError(throwable: Throwable) {
// No op
}
override fun resetViewModel() {
loginViewModel.handle(LoginAction.ResetResetPassword)
}
override fun invalidate() = withState(loginViewModel) { state ->
when (state.asyncResetMailConfirmed) {
is Fail -> {
// Link in email not yet clicked ?
AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(state.asyncResetMailConfirmed.error))
.setPositiveButton(R.string.ok, null)
.show()
}
is Success -> {
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordMailConfirmationSuccess)
}
}
Unit
}
}

View file

@ -16,13 +16,10 @@
package im.vector.riotx.features.login
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import butterknife.OnClick
import im.vector.riotx.R
import im.vector.riotx.core.error.ErrorFormatter
import kotlinx.android.synthetic.main.fragment_login_reset_password_success.*
import javax.inject.Inject
/**
@ -34,19 +31,9 @@ class LoginResetPasswordSuccessFragment @Inject constructor(
override fun getLayoutResId() = R.layout.fragment_login_reset_password_success
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUi()
}
private fun setupUi() {
resetPasswordSuccessNotice.text = getString(R.string.login_reset_password_success_notice, loginViewModel.resetPasswordEmail)
}
@OnClick(R.id.resetPasswordSuccessSubmit)
fun submit() {
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordSuccessDone)
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordMailConfirmationSuccessDone)
}
override fun onRegistrationError(throwable: Throwable) {

View file

@ -92,15 +92,16 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
override fun handle(action: LoginAction) {
when (action) {
is LoginAction.UpdateServerType -> handleUpdateServerType(action)
is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
is LoginAction.InitWith -> handleInitWith(action)
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
is LoginAction.Login -> handleLogin(action)
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
is LoginAction.ResetPassword -> handleResetPassword(action)
is LoginAction.RegisterAction -> handleRegisterAction(action)
is LoginAction.ResetAction -> handleResetAction(action)
is LoginAction.UpdateServerType -> handleUpdateServerType(action)
is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
is LoginAction.InitWith -> handleInitWith(action)
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
is LoginAction.Login -> handleLogin(action)
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
is LoginAction.ResetPassword -> handleResetPassword(action)
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
is LoginAction.RegisterAction -> handleRegisterAction(action)
is LoginAction.ResetAction -> handleResetAction(action)
}
}
@ -280,7 +281,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
resetPasswordEmail = null
setState {
copy(
asyncResetPassword = Uninitialized
asyncResetPassword = Uninitialized,
asyncResetMailConfirmed = Uninitialized
)
}
}
@ -342,6 +344,43 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
}
}
private fun handleResetPasswordMailConfirmed() {
val homeServerConnectionConfigFinal = homeServerConnectionConfig
if (homeServerConnectionConfigFinal == null) {
setState {
copy(
asyncResetMailConfirmed = Fail(Throwable("Bad configuration"))
)
}
} else {
setState {
copy(
asyncResetMailConfirmed = Loading()
)
}
currentTask = authenticator.resetPasswordMailConfirmed(homeServerConnectionConfigFinal, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
setState {
copy(
asyncResetMailConfirmed = Success(data)
)
}
}
override fun onFailure(failure: Throwable) {
// TODO Handled JobCancellationException
setState {
copy(
asyncResetMailConfirmed = Fail(failure)
)
}
}
})
}
}
private fun handleLogin(action: LoginAction.Login) {
val homeServerConnectionConfigFinal = homeServerConnectionConfig

View file

@ -22,14 +22,15 @@ data class LoginViewState(
val asyncLoginAction: Async<Unit> = Uninitialized,
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized,
val asyncResetPassword: Async<Unit> = Uninitialized,
val asyncResetMailConfirmed: Async<Unit> = Uninitialized,
val asyncRegistration: Async<Unit> = Uninitialized
) : MvRxState {
fun isLoading(): Boolean {
// TODO Add other async here
return asyncLoginAction is Loading
|| asyncHomeServerLoginFlowRequest is Loading
|| asyncResetPassword is Loading
|| asyncResetMailConfirmed is Loading
|| asyncRegistration is Loading
}

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView style="@style/LoginLogo" />
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login_reset_password_mail_confirmation_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title" />
<TextView
android:id="@+id/resetPasswordMailConfirmationNotice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
tools:text="@string/login_reset_password_mail_confirmation_notice" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:text="@string/login_reset_password_mail_confirmation_notice_2"
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small" />
<com.google.android.material.button.MaterialButton
android:id="@+id/resetPasswordMailConfirmationSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:text="@string/login_reset_password_mail_confirmation_submit" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -36,6 +36,7 @@
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:text="@string/login_reset_password_success_submit" />

View file

@ -66,10 +66,22 @@
<string name="login_reset_password_submit">Next</string>
<string name="login_reset_password_email_hint">Email</string>
<string name="login_reset_password_password_hint">New password</string>
<string name="login_reset_password_success_title">Check your inbox</string>
<string name="login_reset_password_warning_title">Warning!</string>
<string name="login_reset_password_warning_content">Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.</string>
<string name="login_reset_password_warning_submit">Continue</string>
<string name="login_reset_password_error_not_found">This email is not linked to any account</string>
<string name="login_reset_password_mail_confirmation_title">Check your inbox</string>
<!-- Replaced string is an email -->
<string name="login_reset_password_success_notice">A verification email was sent to %1$s.</string>
<string name="login_reset_password_success_notice_2">Tap on the link to confirm your new password.</string>
<string name="login_reset_password_mail_confirmation_notice">A verification email was sent to %1$s.</string>
<string name="login_reset_password_mail_confirmation_notice_2">Tap on the link to confirm your new password. Once you\'ve followed the link it contains, click below.</string>
<string name="login_reset_password_mail_confirmation_submit">I have verified my email address</string>
<string name="login_reset_password_success_title">Success!</string>
<string name="login_reset_password_success_notice">Your password has been reset.</string>
<string name="login_reset_password_success_notice_2">You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.</string>
<string name="login_reset_password_success_submit">Back to Sign In</string>
<string name="login_set_email_title">Set email address</string>