Login screens: move elements from ViewState to ViewModel

This commit is contained in:
Benoit Marty 2019-11-15 11:47:49 +01:00
parent 55add4734d
commit a1aa16715d
10 changed files with 124 additions and 117 deletions

View File

@ -28,7 +28,7 @@ import im.vector.riotx.core.platform.VectorBaseFragment
*/ */
abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
protected val viewModel: LoginViewModel by activityViewModel() protected val loginViewModel: LoginViewModel by activityViewModel()
protected lateinit var loginSharedActionViewModel: LoginSharedActionViewModel protected lateinit var loginSharedActionViewModel: LoginSharedActionViewModel
@CallSuper @CallSuper
@ -44,6 +44,6 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
return false return false
} }
// Reset any modification of the viewModel by the current fragment // Reset any modification on the loginViewModel by the current fragment
abstract fun resetViewModel() abstract fun resetViewModel()
} }

View File

@ -67,7 +67,7 @@ class LoginActivity : VectorBaseActivity() {
when (it) { when (it) {
is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java) is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java)
is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone() is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone()
is LoginNavigation.OnSignModeSelected -> onSignModeSelected(it) is LoginNavigation.OnSignModeSelected -> onSignModeSelected()
is LoginNavigation.OnLoginFlowRetrieved -> onLoginFlowRetrieved() is LoginNavigation.OnLoginFlowRetrieved -> onLoginFlowRetrieved()
is LoginNavigation.OnSsoLoginFallbackError -> onSsoLoginFallbackError(it) is LoginNavigation.OnSsoLoginFallbackError -> onSsoLoginFallbackError(it)
} }
@ -109,17 +109,16 @@ class LoginActivity : VectorBaseActivity() {
.show() .show()
} }
private fun onServerSelectionDone() = withState(loginViewModel) { private fun onServerSelectionDone() {
when (it.serverType) { when (loginViewModel.serverType) {
ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
ServerType.Modular, ServerType.Modular,
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerUrlFormFragment::class.java) ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerUrlFormFragment::class.java)
} }
} }
private fun onSignModeSelected(mode: LoginNavigation.OnSignModeSelected) { private fun onSignModeSelected() {
// We cannot use the state to get the SignMode, it is not ready... when (loginViewModel.signMode) {
when (mode.signMode) {
SignMode.Unknown -> error("Sign mode has to be set before calling this method") SignMode.Unknown -> error("Sign mode has to be set before calling this method")
SignMode.SignUp -> Unit // TODO addFragmentToBackstack(R.id.loginFragmentContainer, SignUpFragment::class.java) SignMode.SignUp -> Unit // TODO addFragmentToBackstack(R.id.loginFragmentContainer, SignUpFragment::class.java)
SignMode.SignIn -> { SignMode.SignIn -> {

View File

@ -49,6 +49,7 @@ class LoginFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupUi()
setupLoginButton() setupLoginButton()
setupPasswordReveal() setupPasswordReveal()
} }
@ -57,7 +58,32 @@ class LoginFragment @Inject constructor(
val login = loginField.text?.trim().toString() val login = loginField.text?.trim().toString()
val password = passwordField.text?.trim().toString() val password = passwordField.text?.trim().toString()
viewModel.handle(LoginAction.Login(login, password)) loginViewModel.handle(LoginAction.Login(login, password))
}
private fun setupUi() {
when (loginViewModel.serverType) {
ServerType.MatrixOrg -> {
loginServerIcon.isVisible = true
loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
loginTitle.text = getString(R.string.login_connect_to, "matrix.org")
loginNotice.text = getString(R.string.login_server_matrix_org_text)
}
ServerType.Modular -> {
loginServerIcon.isVisible = true
loginServerIcon.setImageResource(R.drawable.ic_logo_modular)
// TODO
loginTitle.text = getString(R.string.login_connect_to, "TODO")
// TODO Remove https://
loginNotice.text = loginViewModel.getHomeServerUrl()
}
ServerType.Other -> {
loginServerIcon.isVisible = false
loginTitle.text = getString(R.string.login_server_other_title)
// TODO Remove https://
loginNotice.text = loginViewModel.getHomeServerUrl()
}
}
} }
private fun setupLoginButton() { private fun setupLoginButton() {
@ -109,33 +135,10 @@ class LoginFragment @Inject constructor(
} }
override fun resetViewModel() { override fun resetViewModel() {
viewModel.handle(LoginAction.ResetLogin) loginViewModel.handle(LoginAction.ResetLogin)
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(loginViewModel) { state ->
when (state.serverType) {
ServerType.MatrixOrg -> {
loginServerIcon.isVisible = true
loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
loginTitle.text = getString(R.string.login_connect_to, "matrix.org")
loginNotice.text = getString(R.string.login_server_matrix_org_text)
}
ServerType.Modular -> {
loginServerIcon.isVisible = true
loginServerIcon.setImageResource(R.drawable.ic_logo_modular)
// TODO
loginTitle.text = getString(R.string.login_connect_to, "TODO")
// TODO Remove https://
loginNotice.text = viewModel.getHomeServerUrl()
}
ServerType.Other -> {
loginServerIcon.isVisible = false
loginTitle.text = getString(R.string.login_server_other_title)
// TODO Remove https://
loginNotice.text = viewModel.getHomeServerUrl()
}
}
when (state.asyncLoginAction) { when (state.asyncLoginAction) {
is Loading -> { is Loading -> {
// Ensure password is hidden // Ensure password is hidden

View File

@ -23,7 +23,7 @@ sealed class LoginNavigation : VectorSharedAction {
object OpenServerSelection : LoginNavigation() object OpenServerSelection : LoginNavigation()
object OnServerSelectionDone : LoginNavigation() object OnServerSelectionDone : LoginNavigation()
object OnLoginFlowRetrieved : LoginNavigation() object OnLoginFlowRetrieved : LoginNavigation()
data class OnSignModeSelected(val signMode: SignMode) : LoginNavigation() object OnSignModeSelected : LoginNavigation()
//object OpenSsoLoginFallback : LoginNavigation() //object OpenSsoLoginFallback : LoginNavigation()
data class OnSsoLoginFallbackError(val errorCode: Int, val description: String, val failingUrl: String) : LoginNavigation() data class OnSsoLoginFallbackError(val errorCode: Int, val description: String, val failingUrl: String) : LoginNavigation()
} }

View File

@ -38,13 +38,16 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
updateSelectedChoice()
initTextViews() initTextViews()
} }
private fun updateSelectedChoice(serverType: ServerType) { private fun updateSelectedChoice() {
loginServerChoiceMatrixOrg.isChecked = serverType == ServerType.MatrixOrg loginViewModel.serverType.let {
loginServerChoiceModular.isChecked = serverType == ServerType.Modular loginServerChoiceMatrixOrg.isChecked = it == ServerType.MatrixOrg
loginServerChoiceOther.isChecked = serverType == ServerType.Other loginServerChoiceModular.isChecked = it == ServerType.Modular
loginServerChoiceOther.isChecked = it == ServerType.Other
}
} }
private fun initTextViews() { private fun initTextViews() {
@ -56,7 +59,6 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
openUrlInExternalBrowser(requireActivity(), "https://example.org") openUrlInExternalBrowser(requireActivity(), "https://example.org")
} }
} }
} }
@OnClick(R.id.loginServerChoiceMatrixOrg) @OnClick(R.id.loginServerChoiceMatrixOrg)
@ -65,7 +67,8 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
// Consider this is a submit // Consider this is a submit
submit() submit()
} else { } else {
viewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg)) loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg))
updateSelectedChoice()
} }
} }
@ -75,7 +78,8 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
// Consider this is a submit // Consider this is a submit
submit() submit()
} else { } else {
viewModel.handle(LoginAction.UpdateServerType(ServerType.Modular)) loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular))
updateSelectedChoice()
} }
} }
@ -85,33 +89,32 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
// Consider this is a submit // Consider this is a submit
submit() submit()
} else { } else {
viewModel.handle(LoginAction.UpdateServerType(ServerType.Other)) loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other))
updateSelectedChoice()
} }
} }
@OnClick(R.id.loginServerSubmit) @OnClick(R.id.loginServerSubmit)
fun submit() = withState(viewModel) { fun submit() {
if (it.serverType == ServerType.MatrixOrg) { if (loginViewModel.serverType == ServerType.MatrixOrg) {
// Request login flow here // Request login flow here
viewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url))) loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url)))
} else { } else {
loginSharedActionViewModel.post(LoginNavigation.OnServerSelectionDone) loginSharedActionViewModel.post(LoginNavigation.OnServerSelectionDone)
} }
} }
override fun resetViewModel() { override fun resetViewModel() {
viewModel.handle(LoginAction.ResetHomeServerType) loginViewModel.handle(LoginAction.ResetHomeServerType)
} }
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(loginViewModel) {
updateSelectedChoice(it.serverType)
when (it.asyncHomeServerLoginFlowRequest) { when (it.asyncHomeServerLoginFlowRequest) {
is Fail -> { is Fail -> {
// TODO Display error in a dialog? // TODO Display error in a dialog?
} }
is Success -> { is Success -> {
// The home server url is valid // LoginFlow for matrix.org has been retrieved
loginSharedActionViewModel.post(LoginNavigation.OnLoginFlowRetrieved) loginSharedActionViewModel.post(LoginNavigation.OnLoginFlowRetrieved)
} }
} }

View File

@ -44,6 +44,11 @@ class LoginServerUrlFormFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupUi()
setupHomeServerField()
}
private fun setupHomeServerField() {
// TODO Import code from Riot to clear error on TIL // TODO Import code from Riot to clear error on TIL
loginServerUrlFormHomeServerUrl.textChanges() loginServerUrlFormHomeServerUrl.textChanges()
.subscribe( .subscribe(
@ -64,39 +69,8 @@ class LoginServerUrlFormFragment @Inject constructor(
} }
} }
@OnClick(R.id.loginServerUrlFormLearnMore) private fun setupUi() {
fun learMore() { when (loginViewModel.serverType) {
// TODO
openUrlInExternalBrowser(requireActivity(), "https://example.org")
}
override fun resetViewModel() {
viewModel.handle(LoginAction.ResetHomeServerUrl)
}
@SuppressLint("SetTextI18n")
@OnClick(R.id.loginServerUrlFormSubmit)
fun submit() {
// Static check of homeserver url, empty, malformed, etc.
var serverUrl = loginServerUrlFormHomeServerUrl.text.toString()
when {
serverUrl.isBlank() -> {
loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server)
}
else -> {
if (serverUrl.startsWith("http").not()) {
serverUrl = "https://$serverUrl"
loginServerUrlFormHomeServerUrl.setText(serverUrl)
}
viewModel.handle(LoginAction.UpdateHomeServer(serverUrl))
}
}
}
override fun invalidate() = withState(viewModel) { state ->
when (state.serverType) {
ServerType.Modular -> { ServerType.Modular -> {
loginServerUrlFormIcon.isVisible = true loginServerUrlFormIcon.isVisible = true
loginServerUrlFormTitle.text = getString(R.string.login_connect_to_modular) loginServerUrlFormTitle.text = getString(R.string.login_connect_to_modular)
@ -115,7 +89,40 @@ class LoginServerUrlFormFragment @Inject constructor(
} }
else -> error("This fragment should not be display in matrix.org mode") else -> error("This fragment should not be display in matrix.org mode")
} }
}
@OnClick(R.id.loginServerUrlFormLearnMore)
fun learMore() {
// TODO
openUrlInExternalBrowser(requireActivity(), "https://example.org")
}
override fun resetViewModel() {
loginViewModel.handle(LoginAction.ResetHomeServerUrl)
}
@SuppressLint("SetTextI18n")
@OnClick(R.id.loginServerUrlFormSubmit)
fun submit() {
// Static check of homeserver url, empty, malformed, etc.
var serverUrl = loginServerUrlFormHomeServerUrl.text.toString()
when {
serverUrl.isBlank() -> {
loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server)
}
else -> {
if (serverUrl.startsWith("http").not()) {
serverUrl = "https://$serverUrl"
loginServerUrlFormHomeServerUrl.setText(serverUrl)
}
loginViewModel.handle(LoginAction.UpdateHomeServer(serverUrl))
}
}
}
override fun invalidate() = withState(loginViewModel) { state ->
when (state.asyncHomeServerLoginFlowRequest) { when (state.asyncHomeServerLoginFlowRequest) {
is Fail -> { is Fail -> {
// TODO Error text is not correct // TODO Error text is not correct

View File

@ -16,9 +16,10 @@
package im.vector.riotx.features.login package im.vector.riotx.features.login
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import butterknife.OnClick import butterknife.OnClick
import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.* import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
import javax.inject.Inject import javax.inject.Inject
@ -30,8 +31,14 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr
override fun getLayoutResId() = R.layout.fragment_login_signup_signin_selection override fun getLayoutResId() = R.layout.fragment_login_signup_signin_selection
private fun updateViews(serverType: ServerType) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
when (serverType) { super.onViewCreated(view, savedInstanceState)
setupUi()
}
private fun setupUi() {
when (loginViewModel.serverType) {
ServerType.MatrixOrg -> { ServerType.MatrixOrg -> {
loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
loginSignupSigninServerIcon.isVisible = true loginSignupSigninServerIcon.isVisible = true
@ -54,21 +61,17 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr
@OnClick(R.id.loginSignupSigninSignUp) @OnClick(R.id.loginSignupSigninSignUp)
fun signUp() { fun signUp() {
viewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp)) loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected(SignMode.SignUp)) loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
} }
@OnClick(R.id.loginSignupSigninSignIn) @OnClick(R.id.loginSignupSigninSignIn)
fun signIn() { fun signIn() {
viewModel.handle(LoginAction.UpdateSignMode(SignMode.SignIn)) loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignIn))
loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected(SignMode.SignIn)) loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
} }
override fun resetViewModel() { override fun resetViewModel() {
viewModel.handle(LoginAction.ResetSignMode) loginViewModel.handle(LoginAction.ResetSignMode)
}
override fun invalidate() = withState(viewModel) {
updateViews(it.serverType)
} }
} }

View File

@ -74,7 +74,7 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
// the user agent to bypass the limitation of Google, as a quick fix (a proper solution will be to use the SSO SDK) // the user agent to bypass the limitation of Google, as a quick fix (a proper solution will be to use the SSO SDK)
login_sso_fallback_webview.settings.userAgentString = "Mozilla/5.0 Google" login_sso_fallback_webview.settings.userAgentString = "Mozilla/5.0 Google"
homeServerUrl = viewModel.getHomeServerUrl() homeServerUrl = loginViewModel.getHomeServerUrl()
if (!homeServerUrl.endsWith("/")) { if (!homeServerUrl.endsWith("/")) {
homeServerUrl += "/" homeServerUrl += "/"
@ -248,7 +248,7 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
refreshToken = null refreshToken = null
) )
viewModel.handle(LoginAction.SsoLoginSuccess(safeCredentials)) loginViewModel.handle(LoginAction.SsoLoginSuccess(safeCredentials))
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -273,7 +273,7 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
refreshToken = null refreshToken = null
) )
viewModel.handle(LoginAction.SsoLoginSuccess(credentials)) loginViewModel.handle(LoginAction.SsoLoginSuccess(credentials))
} }
} }
} }

View File

@ -55,6 +55,12 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
} }
} }
var serverType: ServerType = ServerType.MatrixOrg
private set
var signMode: SignMode = SignMode.Unknown
private set
private var loginConfig: LoginConfig? = null private var loginConfig: LoginConfig? = null
private var homeServerConnectionConfig: HomeServerConnectionConfig? = null private var homeServerConnectionConfig: HomeServerConnectionConfig? = null
@ -93,16 +99,12 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
} }
} }
LoginAction.ResetHomeServerType -> { LoginAction.ResetHomeServerType -> {
setState { serverType = ServerType.MatrixOrg
copy(
serverType = ServerType.MatrixOrg
)
}
} }
LoginAction.ResetSignMode -> { LoginAction.ResetSignMode -> {
signMode = SignMode.Unknown
setState { setState {
copy( copy(
signMode = SignMode.Unknown,
asyncHomeServerLoginFlowRequest = Uninitialized asyncHomeServerLoginFlowRequest = Uninitialized
) )
} }
@ -111,19 +113,11 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
} }
private fun handleUpdateSignMode(action: LoginAction.UpdateSignMode) { private fun handleUpdateSignMode(action: LoginAction.UpdateSignMode) {
setState { signMode = action.signMode
copy(
signMode = action.signMode
)
}
} }
private fun handleUpdateServerType(action: LoginAction.UpdateServerType) { private fun handleUpdateServerType(action: LoginAction.UpdateServerType) {
setState { serverType = action.serverType
copy(
serverType = action.serverType
)
}
} }
private fun handleInitWith(action: LoginAction.InitWith) { private fun handleInitWith(action: LoginAction.InitWith) {

View File

@ -19,8 +19,6 @@ package im.vector.riotx.features.login
import com.airbnb.mvrx.* import com.airbnb.mvrx.*
data class LoginViewState( data class LoginViewState(
val serverType: ServerType = ServerType.MatrixOrg,
val signMode: SignMode = SignMode.Unknown,
val asyncLoginAction: Async<Unit> = Uninitialized, val asyncLoginAction: Async<Unit> = Uninitialized,
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized
) : MvRxState { ) : MvRxState {