Login screens: reset password WIP
This commit is contained in:
parent
810b226f21
commit
90027cc4d5
@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to authenticate to a matrix server.
|
* 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 {
|
interface Authenticator {
|
||||||
|
|
||||||
@ -78,4 +79,9 @@ interface Authenticator {
|
|||||||
* Reset user password
|
* Reset user password
|
||||||
*/
|
*/
|
||||||
fun resetPassword(homeServerConnectionConfig: HomeServerConnectionConfig, email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable
|
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
|
||||||
}
|
}
|
||||||
|
@ -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.LoginFlowResponse
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.auth.registration.*
|
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 im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.*
|
import retrofit2.http.*
|
||||||
@ -66,4 +67,16 @@ internal interface AuthAPI {
|
|||||||
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||||
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
|
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>
|
||||||
}
|
}
|
||||||
|
@ -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.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
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.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.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
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.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
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.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
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.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
@ -39,8 +45,15 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import java.util.*
|
||||||
import javax.inject.Inject
|
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
|
internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
||||||
private val okHttpClient: Lazy<OkHttpClient>,
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
private val retrofitFactory: RetrofitFactory,
|
private val retrofitFactory: RetrofitFactory,
|
||||||
@ -48,6 +61,10 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
|||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val sessionManager: SessionManager
|
private val sessionManager: SessionManager
|
||||||
) : Authenticator {
|
) : Authenticator {
|
||||||
|
private var clientSecret = UUID.randomUUID().toString()
|
||||||
|
private var sendAttempt = 0
|
||||||
|
|
||||||
|
private var resetPasswordData: ResetPasswordData? = null
|
||||||
|
|
||||||
override fun hasAuthenticatedSessions(): Boolean {
|
override fun hasAuthenticatedSessions(): Boolean {
|
||||||
return sessionParamsStore.getLast() != null
|
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 {
|
override fun resetPassword(homeServerConnectionConfig: HomeServerConnectionConfig, email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
resetPasswordInternal(/*homeServerConnectionConfig, email, newPassword*/)
|
resetPasswordInternal(homeServerConnectionConfig, email, newPassword)
|
||||||
}
|
}
|
||||||
result.foldToCallback(callback)
|
result.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
return CancelableCoroutine(job)
|
return CancelableCoroutine(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetPasswordInternal(/*homeServerConnectionConfig: HomeServerConnectionConfig, email: String, newPassword: String*/) {
|
private suspend fun resetPasswordInternal(homeServerConnectionConfig: HomeServerConnectionConfig, email: String, newPassword: String) {
|
||||||
// TODO
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
error("Not implemented")
|
|
||||||
// val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
val param = RegisterAddThreePidTask.Params(
|
||||||
// executeRequest<LoginFlowResponse> {
|
RegisterThreePid.Email(email),
|
||||||
// apiCall = authAPI.getLoginFlows()
|
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 {
|
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||||
|
@ -28,8 +28,11 @@ internal data class AuthParams(
|
|||||||
@Json(name = "type")
|
@Json(name = "type")
|
||||||
val type: String,
|
val type: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: session can be null for reset password request
|
||||||
|
*/
|
||||||
@Json(name = "session")
|
@Json(name = "session")
|
||||||
val session: String,
|
val session: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parameter for "m.login.recaptcha" type
|
* parameter for "m.login.recaptcha" type
|
||||||
@ -72,6 +75,17 @@ internal data class AuthParams(
|
|||||||
threePidCredentials = threePidCredentials
|
threePidCredentials = threePidCredentials
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createForResetPassword(clientSecret: String, sid: String): AuthParams {
|
||||||
|
return AuthParams(
|
||||||
|
type = LoginFlowTypes.EMAIL_IDENTITY,
|
||||||
|
session = null,
|
||||||
|
threePidCredentials = ThreePidCredentials(
|
||||||
|
clientSecret = clientSecret,
|
||||||
|
sid = sid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -130,6 +130,11 @@ interface FragmentModule {
|
|||||||
@FragmentKey(LoginServerUrlFormFragment::class)
|
@FragmentKey(LoginServerUrlFormFragment::class)
|
||||||
fun bindLoginServerUrlFormFragment(fragment: LoginServerUrlFormFragment): Fragment
|
fun bindLoginServerUrlFormFragment(fragment: LoginServerUrlFormFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(LoginResetPasswordMailConfirmationFragment::class)
|
||||||
|
fun bindLoginResetPasswordMailConfirmationFragment(fragment: LoginResetPasswordMailConfirmationFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(LoginResetPasswordFragment::class)
|
@FragmentKey(LoginResetPasswordFragment::class)
|
||||||
|
@ -67,6 +67,9 @@ class ErrorFormatter @Inject constructor(private val stringProvider: StringProvi
|
|||||||
throwable.error.code == MatrixError.LIMIT_EXCEEDED -> {
|
throwable.error.code == MatrixError.LIMIT_EXCEEDED -> {
|
||||||
stringProvider.getString(R.string.login_error_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 -> {
|
else -> {
|
||||||
throwable.error.message.takeIf { it.isNotEmpty() }
|
throwable.error.message.takeIf { it.isNotEmpty() }
|
||||||
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
||||||
|
@ -28,6 +28,7 @@ sealed class LoginAction : VectorViewModelAction {
|
|||||||
data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
|
data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
|
||||||
data class InitWith(val loginConfig: LoginConfig) : LoginAction()
|
data class InitWith(val loginConfig: LoginConfig) : LoginAction()
|
||||||
data class ResetPassword(val email: String, val newPassword: String) : LoginAction()
|
data class ResetPassword(val email: String, val newPassword: String) : LoginAction()
|
||||||
|
object ResetPasswordMailConfirmed : LoginAction()
|
||||||
|
|
||||||
// Register actions
|
// Register actions
|
||||||
open class RegisterAction : LoginAction()
|
open class RegisterAction : LoginAction()
|
||||||
|
@ -72,7 +72,9 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||||||
loginSharedActionViewModel = viewModelProvider.get(LoginSharedActionViewModel::class.java)
|
loginSharedActionViewModel = viewModelProvider.get(LoginSharedActionViewModel::class.java)
|
||||||
loginSharedActionViewModel.observe()
|
loginSharedActionViewModel.observe()
|
||||||
.subscribe {
|
.subscribe {
|
||||||
when (it) {
|
// 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,
|
is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java,
|
||||||
option = { ft ->
|
option = { ft ->
|
||||||
val view = findViewById<View?>(R.id.loginSplashLogo)
|
val view = findViewById<View?>(R.id.loginSplashLogo)
|
||||||
@ -85,11 +87,15 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||||||
is LoginNavigation.OnLoginFlowRetrieved -> onLoginFlowRetrieved()
|
is LoginNavigation.OnLoginFlowRetrieved -> onLoginFlowRetrieved()
|
||||||
is LoginNavigation.OnWebLoginError -> onWebLoginError(it)
|
is LoginNavigation.OnWebLoginError -> onWebLoginError(it)
|
||||||
is LoginNavigation.OnForgetPasswordClicked -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginResetPasswordFragment::class.java)
|
is LoginNavigation.OnForgetPasswordClicked -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginResetPasswordFragment::class.java)
|
||||||
is LoginNavigation.OnResetPasswordSuccess -> {
|
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)
|
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer, LoginResetPasswordSuccessFragment::class.java)
|
addFragmentToBackstack(R.id.loginFragmentContainer, LoginResetPasswordSuccessFragment::class.java)
|
||||||
}
|
}
|
||||||
is LoginNavigation.OnResetPasswordSuccessDone -> supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
is LoginNavigation.OnResetPasswordMailConfirmationSuccessDone -> supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||||
is LoginNavigation.OnSendEmailSuccess -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
is LoginNavigation.OnSendEmailSuccess -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
LoginWaitForEmailFragment::class.java,
|
LoginWaitForEmailFragment::class.java,
|
||||||
LoginWaitForEmailFragmentArgument(it.email),
|
LoginWaitForEmailFragmentArgument(it.email),
|
||||||
|
@ -25,8 +25,10 @@ sealed class LoginNavigation : VectorSharedAction {
|
|||||||
object OnLoginFlowRetrieved : LoginNavigation()
|
object OnLoginFlowRetrieved : LoginNavigation()
|
||||||
object OnSignModeSelected : LoginNavigation()
|
object OnSignModeSelected : LoginNavigation()
|
||||||
object OnForgetPasswordClicked : LoginNavigation()
|
object OnForgetPasswordClicked : LoginNavigation()
|
||||||
object OnResetPasswordSuccess : LoginNavigation()
|
object OnResetPasswordSendThreePidDone : LoginNavigation()
|
||||||
object OnResetPasswordSuccessDone : LoginNavigation()
|
object OnResetPasswordMailConfirmationSuccess : LoginNavigation()
|
||||||
|
object OnResetPasswordMailConfirmationSuccessDone : LoginNavigation()
|
||||||
|
|
||||||
data class OnSendEmailSuccess(val email: String) : LoginNavigation()
|
data class OnSendEmailSuccess(val email: String) : LoginNavigation()
|
||||||
data class OnSendMsisdnSuccess(val msisdn: String) : LoginNavigation()
|
data class OnSendMsisdnSuccess(val msisdn: String) : LoginNavigation()
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ import io.reactivex.rxkotlin.subscribeBy
|
|||||||
import kotlinx.android.synthetic.main.fragment_login.passwordField
|
import kotlinx.android.synthetic.main.fragment_login.passwordField
|
||||||
import kotlinx.android.synthetic.main.fragment_login.passwordFieldTil
|
import kotlinx.android.synthetic.main.fragment_login.passwordFieldTil
|
||||||
import kotlinx.android.synthetic.main.fragment_login.passwordReveal
|
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 kotlinx.android.synthetic.main.fragment_login_reset_password.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -49,6 +48,9 @@ class LoginResetPasswordFragment @Inject constructor(
|
|||||||
|
|
||||||
private var passwordShown = false
|
private var passwordShown = false
|
||||||
|
|
||||||
|
// Show warning only once
|
||||||
|
private var showWarning = true
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_login_reset_password
|
override fun getLayoutResId() = R.layout.fragment_login_reset_password
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@ -84,6 +86,23 @@ class LoginResetPasswordFragment @Inject constructor(
|
|||||||
fun submit() {
|
fun submit() {
|
||||||
cleanupUi()
|
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 email = resetPasswordEmail.text.toString()
|
||||||
val password = passwordField.text.toString()
|
val password = passwordField.text.toString()
|
||||||
|
|
||||||
@ -141,11 +160,10 @@ class LoginResetPasswordFragment @Inject constructor(
|
|||||||
renderPasswordField()
|
renderPasswordField()
|
||||||
}
|
}
|
||||||
is Fail -> {
|
is Fail -> {
|
||||||
resetPasswordEmailTil.error = " "
|
resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
|
||||||
passwordFieldTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
|
|
||||||
}
|
}
|
||||||
is Success -> {
|
is Success -> {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordSuccess)
|
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordSendThreePidDone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -16,13 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.login
|
package im.vector.riotx.features.login
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import kotlinx.android.synthetic.main.fragment_login_reset_password_success.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,19 +31,9 @@ class LoginResetPasswordSuccessFragment @Inject constructor(
|
|||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_login_reset_password_success
|
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)
|
@OnClick(R.id.resetPasswordSuccessSubmit)
|
||||||
fun submit() {
|
fun submit() {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordSuccessDone)
|
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordMailConfirmationSuccessDone)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRegistrationError(throwable: Throwable) {
|
override fun onRegistrationError(throwable: Throwable) {
|
||||||
|
@ -99,6 +99,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||||||
is LoginAction.Login -> handleLogin(action)
|
is LoginAction.Login -> handleLogin(action)
|
||||||
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||||
is LoginAction.ResetPassword -> handleResetPassword(action)
|
is LoginAction.ResetPassword -> handleResetPassword(action)
|
||||||
|
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
||||||
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
||||||
is LoginAction.ResetAction -> handleResetAction(action)
|
is LoginAction.ResetAction -> handleResetAction(action)
|
||||||
}
|
}
|
||||||
@ -280,7 +281,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||||||
resetPasswordEmail = null
|
resetPasswordEmail = null
|
||||||
setState {
|
setState {
|
||||||
copy(
|
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) {
|
private fun handleLogin(action: LoginAction.Login) {
|
||||||
val homeServerConnectionConfigFinal = homeServerConnectionConfig
|
val homeServerConnectionConfigFinal = homeServerConnectionConfig
|
||||||
|
|
||||||
|
@ -22,14 +22,15 @@ data class LoginViewState(
|
|||||||
val asyncLoginAction: Async<Unit> = Uninitialized,
|
val asyncLoginAction: Async<Unit> = Uninitialized,
|
||||||
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized,
|
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized,
|
||||||
val asyncResetPassword: Async<Unit> = Uninitialized,
|
val asyncResetPassword: Async<Unit> = Uninitialized,
|
||||||
|
val asyncResetMailConfirmed: Async<Unit> = Uninitialized,
|
||||||
val asyncRegistration: Async<Unit> = Uninitialized
|
val asyncRegistration: Async<Unit> = Uninitialized
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
fun isLoading(): Boolean {
|
fun isLoading(): Boolean {
|
||||||
// TODO Add other async here
|
|
||||||
return asyncLoginAction is Loading
|
return asyncLoginAction is Loading
|
||||||
|| asyncHomeServerLoginFlowRequest is Loading
|
|| asyncHomeServerLoginFlowRequest is Loading
|
||||||
|| asyncResetPassword is Loading
|
|| asyncResetPassword is Loading
|
||||||
|
|| asyncResetMailConfirmed is Loading
|
||||||
|| asyncRegistration is Loading
|
|| asyncRegistration is Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
@ -36,6 +36,7 @@
|
|||||||
style="@style/Style.Vector.Login.Button"
|
style="@style/Style.Vector.Login.Button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_marginTop="@dimen/layout_vertical_margin"
|
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||||
android:text="@string/login_reset_password_success_submit" />
|
android:text="@string/login_reset_password_success_submit" />
|
||||||
|
|
||||||
|
@ -66,10 +66,22 @@
|
|||||||
<string name="login_reset_password_submit">Next</string>
|
<string name="login_reset_password_submit">Next</string>
|
||||||
<string name="login_reset_password_email_hint">Email</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_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 -->
|
<!-- 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_mail_confirmation_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_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_reset_password_success_submit">Back to Sign In</string>
|
||||||
|
|
||||||
<string name="login_set_email_title">Set email address</string>
|
<string name="login_set_email_title">Set email address</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user