Login screens: move user choices to the ViewState
This commit is contained in:
parent
7ce8a13ddf
commit
d5c2c1938c
@ -21,6 +21,7 @@ import android.view.View
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
import im.vector.riotx.R
|
||||
@ -36,6 +37,8 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||
protected val loginViewModel: LoginViewModel by activityViewModel()
|
||||
protected lateinit var loginSharedActionViewModel: LoginSharedActionViewModel
|
||||
|
||||
private var isResetPasswordStarted = false
|
||||
|
||||
@CallSuper
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@ -53,7 +56,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||
private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
|
||||
when (loginViewEvents) {
|
||||
is LoginViewEvents.Error -> showError(loginViewEvents.throwable)
|
||||
else ->
|
||||
else ->
|
||||
// This is handled by the Activity
|
||||
Unit
|
||||
}
|
||||
@ -81,7 +84,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||
|
||||
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||
return when {
|
||||
loginViewModel.isRegistrationStarted -> {
|
||||
loginViewModel.isRegistrationStarted -> {
|
||||
// Ask for confirmation before cancelling the registration
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.login_signup_cancel_confirmation_title)
|
||||
@ -95,7 +98,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||
|
||||
true
|
||||
}
|
||||
loginViewModel.isResetPasswordStarted -> {
|
||||
isResetPasswordStarted -> {
|
||||
// Ask for confirmation before cancelling the reset password
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.login_reset_password_cancel_confirmation_title)
|
||||
@ -109,7 +112,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
else -> {
|
||||
resetViewModel()
|
||||
// Do not consume the Back event
|
||||
false
|
||||
@ -117,6 +120,17 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||
}
|
||||
}
|
||||
|
||||
final override fun invalidate() = withState(loginViewModel) { state ->
|
||||
// True when email is sent with success to the homeserver
|
||||
isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not()
|
||||
|
||||
updateWithState(state)
|
||||
}
|
||||
|
||||
open fun updateWithState(state: LoginViewState) {
|
||||
// No op by default
|
||||
}
|
||||
|
||||
// Reset any modification on the loginViewModel by the current fragment
|
||||
abstract fun resetViewModel()
|
||||
}
|
||||
|
@ -24,16 +24,17 @@ sealed class LoginAction : VectorViewModelAction {
|
||||
data class UpdateServerType(val serverType: ServerType) : LoginAction()
|
||||
data class UpdateHomeServer(val homeServerUrl: String) : LoginAction()
|
||||
data class UpdateSignMode(val signMode: SignMode) : LoginAction()
|
||||
data class Login(val login: String, val password: String, val initialDeviceName: String) : LoginAction()
|
||||
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()
|
||||
|
||||
// Login or Register, depending on the signMode
|
||||
data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : LoginAction()
|
||||
|
||||
// Register actions
|
||||
open class RegisterAction : LoginAction()
|
||||
|
||||
data class RegisterWith(val username: String, val password: String, val initialDeviceName: String) : RegisterAction()
|
||||
data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction()
|
||||
object SendAgainThreePid : RegisterAction()
|
||||
// TODO Confirm Email (from link in the email, open in the phone, intercepted by RiotX)
|
||||
|
@ -196,29 +196,27 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onServerSelectionDone() {
|
||||
when (loginViewModel.serverType) {
|
||||
private fun onServerSelectionDone() = withState(loginViewModel) { state ->
|
||||
when (state.serverType) {
|
||||
ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
|
||||
ServerType.Modular,
|
||||
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerUrlFormFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSignModeSelected() {
|
||||
when (loginViewModel.signMode) {
|
||||
private fun onSignModeSelected() = withState(loginViewModel) { state ->
|
||||
when (state.signMode) {
|
||||
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
||||
SignMode.SignUp -> {
|
||||
// This is managed by the LoginViewEvents
|
||||
}
|
||||
SignMode.SignIn -> {
|
||||
// It depends on the LoginMode
|
||||
withState(loginViewModel) {
|
||||
when (val loginMode = it.asyncHomeServerLoginFlowRequest.invoke()) {
|
||||
null -> error("Developer error")
|
||||
LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragment::class.java, tag = FRAGMENT_LOGIN_TAG)
|
||||
LoginMode.Sso -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginWebFragment::class.java)
|
||||
is LoginMode.Unsupported -> onLoginModeNotSupported(loginMode)
|
||||
}
|
||||
when (val loginMode = state.asyncHomeServerLoginFlowRequest.invoke()) {
|
||||
null -> error("Developer error")
|
||||
LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragment::class.java, tag = FRAGMENT_LOGIN_TAG)
|
||||
LoginMode.Sso -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginWebFragment::class.java)
|
||||
is LoginMode.Unsupported -> onLoginModeNotSupported(loginMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,8 @@ import android.content.DialogInterface
|
||||
import android.graphics.Bitmap
|
||||
import android.net.http.SslError
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.webkit.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
@ -57,14 +55,10 @@ class LoginCaptchaFragment @Inject constructor(
|
||||
|
||||
private val params: LoginCaptchaFragmentArgument by args()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupWebView()
|
||||
}
|
||||
private var isWebViewLoaded = false
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebView() {
|
||||
private fun setupWebView(state: LoginViewState) {
|
||||
loginCaptchaWevView.settings.javaScriptEnabled = true
|
||||
|
||||
val reCaptchaPage = assetReader.readAssetFile("reCaptchaPage.html") ?: error("missing asset reCaptchaPage.html")
|
||||
@ -73,7 +67,7 @@ class LoginCaptchaFragment @Inject constructor(
|
||||
val mime = "text/html"
|
||||
val encoding = "utf-8"
|
||||
|
||||
val homeServerUrl = loginViewModel.homeServerUrl ?: error("missing url of homeserver")
|
||||
val homeServerUrl = state.homeServerUrl ?: error("missing url of homeserver")
|
||||
loginCaptchaWevView.loadDataWithBaseURL(homeServerUrl, html, mime, encoding, null)
|
||||
loginCaptchaWevView.requestLayout()
|
||||
|
||||
@ -189,4 +183,11 @@ class LoginCaptchaFragment @Inject constructor(
|
||||
override fun resetViewModel() {
|
||||
loginViewModel.handle(LoginAction.ResetLogin)
|
||||
}
|
||||
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
if (!isWebViewLoaded) {
|
||||
setupWebView(state)
|
||||
isWebViewLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import butterknife.OnClick
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
@ -57,16 +56,13 @@ class LoginFragment @Inject constructor(
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupUi()
|
||||
setupAutoFill()
|
||||
setupSubmitButton()
|
||||
setupPasswordReveal()
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
private fun setupAutoFill() {
|
||||
private fun setupAutoFill(state: LoginViewState) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
when (loginViewModel.signMode) {
|
||||
when (state.signMode) {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> {
|
||||
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
|
||||
@ -87,11 +83,7 @@ class LoginFragment @Inject constructor(
|
||||
val login = loginField.text.toString()
|
||||
val password = passwordField.text.toString()
|
||||
|
||||
when (loginViewModel.signMode) {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> loginViewModel.handle(LoginAction.RegisterWith(login, password, getString(R.string.login_mobile_device)))
|
||||
SignMode.SignIn -> loginViewModel.handle(LoginAction.Login(login, password, getString(R.string.login_mobile_device)))
|
||||
}
|
||||
loginViewModel.handle(LoginAction.LoginOrRegister(login, password, getString(R.string.login_mobile_device)))
|
||||
}
|
||||
|
||||
private fun cleanupUi() {
|
||||
@ -100,18 +92,18 @@ class LoginFragment @Inject constructor(
|
||||
passwordFieldTil.error = null
|
||||
}
|
||||
|
||||
private fun setupUi() {
|
||||
val resId = when (loginViewModel.signMode) {
|
||||
private fun setupUi(state: LoginViewState) {
|
||||
val resId = when (state.signMode) {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> R.string.login_signup_to
|
||||
SignMode.SignIn -> R.string.login_connect_to
|
||||
}
|
||||
|
||||
when (loginViewModel.serverType) {
|
||||
when (state.serverType) {
|
||||
ServerType.MatrixOrg -> {
|
||||
loginServerIcon.isVisible = true
|
||||
loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
|
||||
loginTitle.text = getString(resId, loginViewModel.getHomeServerUrlSimple())
|
||||
loginTitle.text = getString(resId, state.homeServerUrlSimple)
|
||||
loginNotice.text = getString(R.string.login_server_matrix_org_text)
|
||||
}
|
||||
ServerType.Modular -> {
|
||||
@ -123,16 +115,16 @@ class LoginFragment @Inject constructor(
|
||||
}
|
||||
ServerType.Other -> {
|
||||
loginServerIcon.isVisible = false
|
||||
loginTitle.text = getString(resId, loginViewModel.getHomeServerUrlSimple())
|
||||
loginTitle.text = getString(resId, state.homeServerUrlSimple)
|
||||
loginNotice.text = getString(R.string.login_server_other_text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
forgetPasswordButton.isVisible = loginViewModel.signMode == SignMode.SignIn
|
||||
private fun setupButtons(state: LoginViewState) {
|
||||
forgetPasswordButton.isVisible = state.signMode == SignMode.SignIn
|
||||
|
||||
loginSubmit.text = getString(when (loginViewModel.signMode) {
|
||||
loginSubmit.text = getString(when (state.signMode) {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> R.string.login_signup_submit
|
||||
SignMode.SignIn -> R.string.login_signin
|
||||
@ -193,7 +185,11 @@ class LoginFragment @Inject constructor(
|
||||
loginFieldTil.error = errorFormatter.toHumanReadable(throwable)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(loginViewModel) { state ->
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupUi(state)
|
||||
setupAutoFill(state)
|
||||
setupButtons(state)
|
||||
|
||||
when (state.asyncLoginAction) {
|
||||
is Loading -> {
|
||||
// Ensure password is hidden
|
||||
|
@ -23,7 +23,6 @@ import butterknife.OnClick
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
@ -53,13 +52,12 @@ class LoginResetPasswordFragment @Inject constructor(
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupUi()
|
||||
setupSubmitButton()
|
||||
setupPasswordReveal()
|
||||
}
|
||||
|
||||
private fun setupUi() {
|
||||
resetPasswordTitle.text = getString(R.string.login_reset_password_on, loginViewModel.getHomeServerUrlSimple())
|
||||
private fun setupUi(state: LoginViewState) {
|
||||
resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrlSimple)
|
||||
}
|
||||
|
||||
private fun setupSubmitButton() {
|
||||
@ -148,7 +146,9 @@ class LoginResetPasswordFragment @Inject constructor(
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(loginViewModel) { state ->
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupUi(state)
|
||||
|
||||
when (state.asyncResetPassword) {
|
||||
is Loading -> {
|
||||
// Ensure new password is hidden
|
||||
|
@ -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 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 im.vector.riotx.core.error.is401
|
||||
@ -38,14 +35,8 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor(
|
||||
|
||||
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() {
|
||||
resetPasswordMailConfirmationNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, loginViewModel.resetPasswordEmail)
|
||||
private fun setupUi(state: LoginViewState) {
|
||||
resetPasswordMailConfirmationNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, state.resetPasswordEmail)
|
||||
}
|
||||
|
||||
@OnClick(R.id.resetPasswordMailConfirmationSubmit)
|
||||
@ -65,7 +56,9 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor(
|
||||
loginViewModel.handle(LoginAction.ResetResetPassword)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(loginViewModel) { state ->
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupUi(state)
|
||||
|
||||
when (state.asyncResetMailConfirmed) {
|
||||
is Fail -> {
|
||||
// Link in email not yet clicked ?
|
||||
@ -85,7 +78,5 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor(
|
||||
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordMailConfirmationSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
Unit
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.transition.TransitionInflater
|
||||
import butterknife.OnClick
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotx.R
|
||||
@ -52,12 +51,11 @@ class LoginServerSelectionFragment @Inject constructor(
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
updateSelectedChoice()
|
||||
initTextViews()
|
||||
}
|
||||
|
||||
private fun updateSelectedChoice() {
|
||||
loginViewModel.serverType.let {
|
||||
private fun updateSelectedChoice(state: LoginViewState) {
|
||||
state.serverType.let {
|
||||
loginServerChoiceMatrixOrg.isChecked = it == ServerType.MatrixOrg
|
||||
loginServerChoiceModular.isChecked = it == ServerType.Modular
|
||||
loginServerChoiceOther.isChecked = it == ServerType.Other
|
||||
@ -83,7 +81,6 @@ class LoginServerSelectionFragment @Inject constructor(
|
||||
submit()
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg))
|
||||
updateSelectedChoice()
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +91,6 @@ class LoginServerSelectionFragment @Inject constructor(
|
||||
submit()
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular))
|
||||
updateSelectedChoice()
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,13 +101,12 @@ class LoginServerSelectionFragment @Inject constructor(
|
||||
submit()
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other))
|
||||
updateSelectedChoice()
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginServerSubmit)
|
||||
fun submit() {
|
||||
if (loginViewModel.serverType == ServerType.MatrixOrg) {
|
||||
fun submit() = withState(loginViewModel) { state ->
|
||||
if (state.serverType == ServerType.MatrixOrg) {
|
||||
// Request login flow here
|
||||
loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url)))
|
||||
} else {
|
||||
@ -131,11 +126,10 @@ class LoginServerSelectionFragment @Inject constructor(
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(loginViewModel) {
|
||||
when (it.asyncHomeServerLoginFlowRequest) {
|
||||
is Fail -> {
|
||||
// TODO Display error in a dialog?
|
||||
}
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
updateSelectedChoice(state)
|
||||
|
||||
when (state.asyncHomeServerLoginFlowRequest) {
|
||||
is Success -> {
|
||||
// LoginFlow for matrix.org has been retrieved
|
||||
loginSharedActionViewModel.post(LoginNavigation.OnLoginFlowRetrieved)
|
||||
|
@ -23,7 +23,6 @@ import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.view.isVisible
|
||||
import butterknife.OnClick
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
@ -44,7 +43,6 @@ class LoginServerUrlFormFragment @Inject constructor(
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupUi()
|
||||
setupHomeServerField()
|
||||
}
|
||||
|
||||
@ -65,8 +63,8 @@ class LoginServerUrlFormFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupUi() {
|
||||
when (loginViewModel.serverType) {
|
||||
private fun setupUi(state: LoginViewState) {
|
||||
when (state.serverType) {
|
||||
ServerType.Modular -> {
|
||||
loginServerUrlFormIcon.isVisible = true
|
||||
loginServerUrlFormTitle.text = getString(R.string.login_connect_to_modular)
|
||||
@ -127,7 +125,9 @@ class LoginServerUrlFormFragment @Inject constructor(
|
||||
loginServerUrlFormHomeServerUrlTil.error = errorFormatter.toHumanReadable(throwable)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(loginViewModel) { state ->
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupUi(state)
|
||||
|
||||
when (state.asyncHomeServerLoginFlowRequest) {
|
||||
is Success -> {
|
||||
// The home server url is valid
|
||||
|
@ -16,12 +16,9 @@
|
||||
|
||||
package im.vector.riotx.features.login
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import butterknife.OnClick
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
|
||||
@ -38,21 +35,12 @@ class LoginSignUpSignInSelectionFragment @Inject constructor(
|
||||
|
||||
private var isSsoSignIn: Boolean = false
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
isSsoSignIn = withState(loginViewModel) { it.asyncHomeServerLoginFlowRequest.invoke() } == LoginMode.Sso
|
||||
|
||||
setupUi()
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
private fun setupUi() {
|
||||
when (loginViewModel.serverType) {
|
||||
private fun setupUi(state: LoginViewState) {
|
||||
when (state.serverType) {
|
||||
ServerType.MatrixOrg -> {
|
||||
loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
|
||||
loginSignupSigninServerIcon.isVisible = true
|
||||
loginSignupSigninTitle.text = getString(R.string.login_connect_to, loginViewModel.getHomeServerUrlSimple())
|
||||
loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrlSimple)
|
||||
loginSignupSigninText.text = getString(R.string.login_server_matrix_org_text)
|
||||
}
|
||||
ServerType.Modular -> {
|
||||
@ -60,17 +48,19 @@ class LoginSignUpSignInSelectionFragment @Inject constructor(
|
||||
loginSignupSigninServerIcon.isVisible = true
|
||||
// TODO
|
||||
loginSignupSigninTitle.text = getString(R.string.login_connect_to, "TODO MODULAR NAME")
|
||||
loginSignupSigninText.text = loginViewModel.getHomeServerUrlSimple()
|
||||
loginSignupSigninText.text = state.homeServerUrlSimple
|
||||
}
|
||||
ServerType.Other -> {
|
||||
loginSignupSigninServerIcon.isVisible = false
|
||||
loginSignupSigninTitle.text = getString(R.string.login_server_other_title)
|
||||
loginSignupSigninText.text = getString(R.string.login_connect_to, loginViewModel.getHomeServerUrlSimple())
|
||||
loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrlSimple)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
private fun setupButtons(state: LoginViewState) {
|
||||
isSsoSignIn = state.asyncHomeServerLoginFlowRequest.invoke() == LoginMode.Sso
|
||||
|
||||
if (isSsoSignIn) {
|
||||
loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
|
||||
loginSignupSigninSignIn.isVisible = false
|
||||
@ -106,4 +96,9 @@ class LoginSignUpSignInSelectionFragment @Inject constructor(
|
||||
override fun resetViewModel() {
|
||||
loginViewModel.handle(LoginAction.ResetSignMode)
|
||||
}
|
||||
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupUi(state)
|
||||
setupButtons(state)
|
||||
}
|
||||
}
|
||||
|
@ -73,24 +73,9 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
var isRegistrationStarted: Boolean = false
|
||||
private set
|
||||
|
||||
// True when email is sent with success to the homeserver
|
||||
val isResetPasswordStarted: Boolean
|
||||
get() = resetPasswordEmail.isNullOrBlank().not()
|
||||
|
||||
private var registrationWizard: RegistrationWizard? = null
|
||||
private var loginWizard: LoginWizard? = null
|
||||
|
||||
// TODO Move all this in a data class
|
||||
var serverType: ServerType = ServerType.MatrixOrg
|
||||
private set
|
||||
var signMode: SignMode = SignMode.Unknown
|
||||
private set
|
||||
var resetPasswordEmail: String? = null
|
||||
private set
|
||||
|
||||
var homeServerUrl: String? = null
|
||||
private set
|
||||
|
||||
private var loginConfig: LoginConfig? = null
|
||||
|
||||
private var currentTask: Cancelable? = null
|
||||
@ -104,7 +89,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||
is LoginAction.InitWith -> handleInitWith(action)
|
||||
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
|
||||
is LoginAction.Login -> handleLogin(action)
|
||||
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action)
|
||||
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||
is LoginAction.ResetPassword -> handleResetPassword(action)
|
||||
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
||||
@ -115,7 +100,6 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
|
||||
private fun handleRegisterAction(action: LoginAction.RegisterAction) {
|
||||
when (action) {
|
||||
is LoginAction.RegisterWith -> handleRegisterWith(action)
|
||||
is LoginAction.CaptchaDone -> handleCaptchaDone(action)
|
||||
is LoginAction.AcceptTerms -> handleAcceptTerms()
|
||||
is LoginAction.RegisterDummy -> handleRegisterDummy()
|
||||
@ -232,7 +216,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
currentTask = registrationWizard?.dummy(registrationCallback)
|
||||
}
|
||||
|
||||
private fun handleRegisterWith(action: LoginAction.RegisterWith) {
|
||||
private fun handleRegisterWith(action: LoginAction.LoginOrRegister) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.createAccount(
|
||||
action.username,
|
||||
@ -270,33 +254,37 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
}
|
||||
LoginAction.ResetHomeServerUrl -> {
|
||||
homeServerUrl = null
|
||||
registrationWizard = null
|
||||
loginWizard = null
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = null
|
||||
)
|
||||
}
|
||||
}
|
||||
LoginAction.ResetHomeServerType -> {
|
||||
serverType = ServerType.MatrixOrg
|
||||
}
|
||||
LoginAction.ResetSignMode -> {
|
||||
signMode = SignMode.Unknown
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
serverType = ServerType.MatrixOrg
|
||||
)
|
||||
}
|
||||
}
|
||||
LoginAction.ResetSignMode -> {
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
signMode = SignMode.Unknown
|
||||
)
|
||||
}
|
||||
}
|
||||
LoginAction.ResetResetPassword -> {
|
||||
resetPasswordEmail = null
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Uninitialized,
|
||||
asyncResetMailConfirmed = Uninitialized
|
||||
asyncResetMailConfirmed = Uninitialized,
|
||||
resetPasswordEmail = null
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -304,17 +292,25 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
|
||||
private fun handleUpdateSignMode(action: LoginAction.UpdateSignMode) {
|
||||
signMode = action.signMode
|
||||
setState {
|
||||
copy(
|
||||
signMode = action.signMode
|
||||
)
|
||||
}
|
||||
|
||||
if (signMode == SignMode.SignUp) {
|
||||
if (action.signMode == SignMode.SignUp) {
|
||||
startRegistrationFlow()
|
||||
} else if (signMode == SignMode.SignIn) {
|
||||
} else if (action.signMode == SignMode.SignIn) {
|
||||
startAuthenticationFlow()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUpdateServerType(action: LoginAction.UpdateServerType) {
|
||||
serverType = action.serverType
|
||||
setState {
|
||||
copy(
|
||||
serverType = action.serverType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInitWith(action: LoginAction.InitWith) {
|
||||
@ -339,11 +335,10 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
|
||||
currentTask = safeLoginWizard.resetPassword(action.email, action.newPassword, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
resetPasswordEmail = action.email
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Success(data)
|
||||
asyncResetPassword = Success(data),
|
||||
resetPasswordEmail = action.email
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -378,11 +373,10 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
|
||||
currentTask = safeLoginWizard.resetPasswordMailConfirmed(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
resetPasswordEmail = null
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Success(data)
|
||||
asyncResetMailConfirmed = Success(data),
|
||||
resetPasswordEmail = null
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -399,7 +393,15 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLogin(action: LoginAction.Login) {
|
||||
private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state ->
|
||||
when (state.signMode) {
|
||||
SignMode.SignIn -> handleLogin(action)
|
||||
SignMode.SignUp -> handleRegisterWith(action)
|
||||
else -> error("Developer error, invalid sign mode")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLogin(action: LoginAction.LoginOrRegister) {
|
||||
val safeLoginWizard = loginWizard
|
||||
|
||||
if (safeLoginWizard == null) {
|
||||
@ -416,7 +418,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.login(
|
||||
action.login,
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName,
|
||||
object : MatrixCallback<Session> {
|
||||
@ -473,8 +475,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWebLoginSuccess(action: LoginAction.WebLoginSuccess) {
|
||||
val homeServerConnectionConfigFinal = homeServerConnectionConfigFactory.create(homeServerUrl)
|
||||
private fun handleWebLoginSuccess(action: LoginAction.WebLoginSuccess) = withState { state ->
|
||||
val homeServerConnectionConfigFinal = homeServerConnectionConfigFactory.create(state.homeServerUrl)
|
||||
|
||||
if (homeServerConnectionConfigFinal == null) {
|
||||
// Should not happen
|
||||
@ -525,9 +527,6 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
|
||||
override fun onSuccess(data: LoginFlowResult) {
|
||||
// Keep the url
|
||||
homeServerUrl = action.homeServerUrl
|
||||
|
||||
when (data) {
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
@ -542,7 +541,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
} else {
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Success(loginMode)
|
||||
asyncHomeServerLoginFlowRequest = Success(loginMode),
|
||||
homeServerUrl = action.homeServerUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -576,13 +576,4 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
fun getInitialHomeServerUrl(): String? {
|
||||
return loginConfig?.homeServerUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* Ex: "https://matrix.org/" -> "matrix.org"
|
||||
*/
|
||||
fun getHomeServerUrlSimple(): String {
|
||||
return (homeServerUrl ?: "")
|
||||
.substringAfter("://")
|
||||
.trim { it == '/' }
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,17 @@ data class LoginViewState(
|
||||
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized,
|
||||
val asyncResetPassword: Async<Unit> = Uninitialized,
|
||||
val asyncResetMailConfirmed: Async<Unit> = Uninitialized,
|
||||
val asyncRegistration: Async<Unit> = Uninitialized
|
||||
val asyncRegistration: Async<Unit> = Uninitialized,
|
||||
|
||||
// User choice
|
||||
@PersistState
|
||||
val serverType: ServerType = ServerType.MatrixOrg,
|
||||
@PersistState
|
||||
val signMode: SignMode = SignMode.Unknown,
|
||||
@PersistState
|
||||
val resetPasswordEmail: String? = null,
|
||||
@PersistState
|
||||
val homeServerUrl: String? = null
|
||||
) : MvRxState {
|
||||
|
||||
fun isLoading(): Boolean {
|
||||
@ -37,4 +47,12 @@ data class LoginViewState(
|
||||
fun isUserLogged(): Boolean {
|
||||
return asyncLoginAction is Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Ex: "https://matrix.org/" -> "matrix.org"
|
||||
*/
|
||||
val homeServerUrlSimple: String
|
||||
get() = (homeServerUrl ?: "")
|
||||
.substringAfter("://")
|
||||
.trim { it == '/' }
|
||||
}
|
||||
|
@ -48,50 +48,48 @@ class LoginWebFragment @Inject constructor(
|
||||
private val errorFormatter: ErrorFormatter
|
||||
) : AbstractLoginFragment() {
|
||||
|
||||
private lateinit var homeServerUrl: String
|
||||
private lateinit var signMode: SignMode
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_login_web
|
||||
|
||||
private var isWebViewLoaded = false
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
homeServerUrl = loginViewModel.homeServerUrl ?: error("Developer error: Invalid url")
|
||||
signMode = loginViewModel.signMode.takeIf { it != SignMode.Unknown } ?: error("Developer error: Invalid sign mode")
|
||||
|
||||
setupToolbar(loginWebToolbar)
|
||||
setupTitle()
|
||||
setupWebView()
|
||||
}
|
||||
|
||||
private fun setupTitle() {
|
||||
loginWebToolbar.title = when (signMode) {
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupTitle(state)
|
||||
if (!isWebViewLoaded) {
|
||||
setupWebView(state)
|
||||
isWebViewLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTitle(state: LoginViewState) {
|
||||
loginWebToolbar.title = when (state.signMode) {
|
||||
SignMode.SignIn -> getString(R.string.login_signin)
|
||||
else -> getString(R.string.login_signup)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebView() {
|
||||
private fun setupWebView(state: LoginViewState) {
|
||||
loginWebWebView.settings.javaScriptEnabled = true
|
||||
|
||||
// Due to https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html, we hack
|
||||
// the user agent to bypass the limitation of Google, as a quick fix (a proper solution will be to use the SSO SDK)
|
||||
loginWebWebView.settings.userAgentString = "Mozilla/5.0 Google"
|
||||
|
||||
if (!homeServerUrl.endsWith("/")) {
|
||||
homeServerUrl += "/"
|
||||
}
|
||||
|
||||
// AppRTC requires third party cookies to work
|
||||
val cookieManager = android.webkit.CookieManager.getInstance()
|
||||
|
||||
// clear the cookies must be cleared
|
||||
if (cookieManager == null) {
|
||||
launchWebView()
|
||||
launchWebView(state)
|
||||
} else {
|
||||
if (!cookieManager.hasCookies()) {
|
||||
launchWebView()
|
||||
launchWebView(state)
|
||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
try {
|
||||
cookieManager.removeAllCookie()
|
||||
@ -99,24 +97,24 @@ class LoginWebFragment @Inject constructor(
|
||||
Timber.e(e, " cookieManager.removeAllCookie() fails")
|
||||
}
|
||||
|
||||
launchWebView()
|
||||
launchWebView(state)
|
||||
} else {
|
||||
try {
|
||||
cookieManager.removeAllCookies { launchWebView() }
|
||||
cookieManager.removeAllCookies { launchWebView(state) }
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, " cookieManager.removeAllCookie() fails")
|
||||
launchWebView()
|
||||
launchWebView(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchWebView() {
|
||||
if (signMode == SignMode.SignIn) {
|
||||
loginWebWebView.loadUrl(homeServerUrl + "_matrix/static/client/login/")
|
||||
private fun launchWebView(state: LoginViewState) {
|
||||
if (state.signMode == SignMode.SignIn) {
|
||||
loginWebWebView.loadUrl(state.homeServerUrl?.trim { it == '/' } + "/_matrix/static/client/login/")
|
||||
} else {
|
||||
// MODE_REGISTER
|
||||
loginWebWebView.loadUrl(homeServerUrl + "_matrix/static/client/register/")
|
||||
loginWebWebView.loadUrl(state.homeServerUrl?.trim { it == '/' } + "/_matrix/static/client/register/")
|
||||
}
|
||||
|
||||
loginWebWebView.webViewClient = object : WebViewClient() {
|
||||
@ -157,7 +155,7 @@ class LoginWebFragment @Inject constructor(
|
||||
val mxcJavascriptSendObjectMessage = assetReader.readAssetFile("sendObject.js")
|
||||
view.loadUrl(mxcJavascriptSendObjectMessage)
|
||||
|
||||
if (signMode == SignMode.SignIn) {
|
||||
if (state.signMode == SignMode.SignIn) {
|
||||
// The function the fallback page calls when the login is complete
|
||||
val mxcJavascriptOnLogin = assetReader.readAssetFile("onLogin.js")
|
||||
view.loadUrl(mxcJavascriptOnLogin)
|
||||
@ -211,7 +209,7 @@ class LoginWebFragment @Inject constructor(
|
||||
if (javascriptResponse != null) {
|
||||
val action = javascriptResponse.action
|
||||
|
||||
if (signMode == SignMode.SignIn) {
|
||||
if (state.signMode == SignMode.SignIn) {
|
||||
try {
|
||||
if (action == "onLogin") {
|
||||
val credentials = javascriptResponse.credentials
|
||||
|
@ -27,6 +27,7 @@ import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.core.utils.openUrlInExternalBrowser
|
||||
import im.vector.riotx.features.login.AbstractLoginFragment
|
||||
import im.vector.riotx.features.login.LoginAction
|
||||
import im.vector.riotx.features.login.LoginViewState
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_login_terms.*
|
||||
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||
@ -56,7 +57,6 @@ class LoginTermsFragment @Inject constructor(
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
loginTermsPolicyList.setController(policyController)
|
||||
policyController.homeServer = loginViewModel.getHomeServerUrlSimple()
|
||||
policyController.listener = this
|
||||
|
||||
val list = ArrayList<LocalizedFlowDataLoginTermsChecked>()
|
||||
@ -67,8 +67,6 @@ class LoginTermsFragment @Inject constructor(
|
||||
}
|
||||
|
||||
loginTermsViewState = LoginTermsViewState(list)
|
||||
|
||||
renderState()
|
||||
}
|
||||
|
||||
private fun renderState() {
|
||||
@ -109,6 +107,11 @@ class LoginTermsFragment @Inject constructor(
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
policyController.homeServer = state.homeServerUrlSimple
|
||||
renderState()
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
loginViewModel.handle(LoginAction.ResetLogin)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user