diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/Authenticator.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/Authenticator.kt index c1aaf11f6f..5d7eba2d8e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/Authenticator.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/Authenticator.kt @@ -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): Cancelable + + /** + * Confirm the new password, once the user has check his email + */ + fun resetPasswordMailConfirmed(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt index 324122ce0e..961fac60ff 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt @@ -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 + + /** + * 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 + + /** + * 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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt index f469b42793..28d7081b07 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt @@ -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, 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): 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 { - // 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 { + apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) + } + + resetPasswordData = ResetPasswordData(newPassword, result) + } + + override fun resetPasswordMailConfirmed(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback): 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 { + apiCall = authAPI.resetPasswordMailConfirmed(param) + } + + // Set to null? + // resetPasswordData = null } private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/AuthParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/AuthParams.kt index 7c71ba1523..ad85579550 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/AuthParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/AuthParams.kt @@ -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 + ) + ) + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/signin/ResetPasswordMailConfirmed.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/signin/ResetPasswordMailConfirmed.kt new file mode 100644 index 0000000000..88fa411f5d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/signin/ResetPasswordMailConfirmed.kt @@ -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 + ) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index d3576c2625..208246aa68 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -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) diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index c943a86fbc..4c148a7e54 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt index 87a8a6c8b2..663a4beecd 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt @@ -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() diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index b80f20f511..3e8d8b639b 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -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(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) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginNavigation.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginNavigation.kt index 14ea19b4e0..79c6409a3f 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginNavigation.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginNavigation.kt @@ -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() diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordFragment.kt index 9c912e07d7..1f585999a1 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordFragment.kt @@ -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) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt new file mode 100644 index 0000000000..15b8a316a9 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt @@ -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 + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordSuccessFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordSuccessFragment.kt index e7a55a4383..21a73aa2a6 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordSuccessFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordSuccessFragment.kt @@ -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) { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index a407e0e8b0..d695a0daa3 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -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 { + 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 diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt index e0af466b57..a10a2c76e3 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt @@ -22,14 +22,15 @@ data class LoginViewState( val asyncLoginAction: Async = Uninitialized, val asyncHomeServerLoginFlowRequest: Async = Uninitialized, val asyncResetPassword: Async = Uninitialized, + val asyncResetMailConfirmed: Async = Uninitialized, val asyncRegistration: Async = 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 } diff --git a/vector/src/main/res/layout/fragment_login_reset_password_mail_confirmation.xml b/vector/src/main/res/layout/fragment_login_reset_password_mail_confirmation.xml new file mode 100644 index 0000000000..5818071d28 --- /dev/null +++ b/vector/src/main/res/layout/fragment_login_reset_password_mail_confirmation.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_login_reset_password_success.xml b/vector/src/main/res/layout/fragment_login_reset_password_success.xml index bffb9c11ef..4f5a0292f5 100644 --- a/vector/src/main/res/layout/fragment_login_reset_password_success.xml +++ b/vector/src/main/res/layout/fragment_login_reset_password_success.xml @@ -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" /> diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index dc89822f2f..674e1b8f72 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -66,10 +66,22 @@ Next Email New password - Check your inbox + + Warning! + 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. + Continue + + This email is not linked to any account + + Check your inbox - A verification email was sent to %1$s. - Tap on the link to confirm your new password. + A verification email was sent to %1$s. + Tap on the link to confirm your new password. Once you\'ve followed the link it contains, click below. + I have verified my email address + + Success! + Your password has been reset. + 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. Back to Sign In Set email address