Login screens: reset state when navigating back

This commit is contained in:
Benoit Marty 2019-11-14 17:42:50 +01:00
parent 6525314af8
commit 7f1f98c2e5
14 changed files with 141 additions and 16 deletions

View file

@ -36,6 +36,7 @@ import im.vector.riotx.features.home.group.GroupListFragment
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.login.LoginFragment
import im.vector.riotx.features.login.LoginServerUrlFormFragment
import im.vector.riotx.features.login.LoginSsoFallbackFragment
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
@ -194,4 +195,10 @@ interface FragmentModule {
@IntoMap
@FragmentKey(PublicRoomsFragment::class)
fun bindPublicRoomsFragment(fragment: PublicRoomsFragment): Fragment
// TODO Add all other LoginFragment
@Binds
@IntoMap
@FragmentKey(LoginServerUrlFormFragment::class)
fun bindLoginServerUrlFormFragment(fragment: LoginServerUrlFormFragment): Fragment
}

View file

@ -20,12 +20,13 @@ import android.os.Bundle
import android.view.View
import androidx.annotation.CallSuper
import com.airbnb.mvrx.activityViewModel
import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.VectorBaseFragment
/**
* Parent Fragment for all the login/registration screens
*/
abstract class AbstractLoginFragment() : VectorBaseFragment() {
abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
protected val viewModel: LoginViewModel by activityViewModel()
protected lateinit var loginSharedActionViewModel: LoginSharedActionViewModel
@ -37,4 +38,12 @@ abstract class AbstractLoginFragment() : VectorBaseFragment() {
loginSharedActionViewModel = activityViewModelProvider.get(LoginSharedActionViewModel::class.java)
}
override fun onBackPressed(): Boolean {
resetViewModel()
// Do not consume the Back event
return false
}
// Reset any modification of the viewModel by the current fragment
abstract fun resetViewModel()
}

View file

@ -20,10 +20,18 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.riotx.core.platform.VectorViewModelAction
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) : LoginAction()
data class SsoLoginSuccess(val credentials: Credentials) : LoginAction()
data class InitWith(val loginConfig: LoginConfig) : LoginAction()
data class UpdateServerType(val serverType: ServerType) : LoginAction()
data class UpdateSignMode(val signMode: SignMode) : LoginAction()
// Reset actions
open class ResetAction : LoginAction()
object ResetHomeServerType : ResetAction()
object ResetHomeServerUrl : ResetAction()
object ResetSignMode : ResetAction()
object ResetLogin : ResetAction()
}

View file

@ -61,7 +61,7 @@ class LoginActivity : VectorBaseActivity() {
when (it) {
is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.simpleFragmentContainer, LoginServerSelectionFragment::class.java)
is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone()
is LoginNavigation.OnSignModeSelected -> onSignModeSelected()
is LoginNavigation.OnSignModeSelected -> onSignModeSelected(it)
is LoginNavigation.OpenSsoLoginFallback -> addFragmentToBackstack(R.id.simpleFragmentContainer, LoginSsoFallbackFragment::class.java)
is LoginNavigation.GoBack -> supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
@ -85,10 +85,12 @@ class LoginActivity : VectorBaseActivity() {
}
}
private fun onSignModeSelected() = withState(loginViewModel) {
when (it.signMode) {
SignMode.SignUp -> Unit // TODO addFragmentToBackstack(R.id.simpleFragmentContainer, SignUpFragment::class.java)
SignMode.SignIn -> addFragmentToBackstack(R.id.simpleFragmentContainer, LoginFragment::class.java)
private fun onSignModeSelected(mode: LoginNavigation.OnSignModeSelected) {
// We cannot use the state, it is not ready...
when (mode.signMode) {
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
SignMode.SignUp -> Unit // TODO addFragmentToBackstack(R.id.simpleFragmentContainer, SignUpFragment::class.java)
SignMode.SignIn -> addFragmentToBackstack(R.id.simpleFragmentContainer, LoginFragment::class.java)
}
}

View file

@ -98,6 +98,10 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
}
}
override fun resetViewModel() {
viewModel.handle(LoginAction.ResetLogin)
}
override fun invalidate() = withState(viewModel) { state ->
TransitionManager.beginDelayedTransition(login_fragment)

View file

@ -22,7 +22,7 @@ import im.vector.riotx.core.platform.VectorSharedAction
sealed class LoginNavigation : VectorSharedAction {
object OpenServerSelection : LoginNavigation()
object OnServerSelectionDone : LoginNavigation()
object OnSignModeSelected : LoginNavigation()
data class OnSignModeSelected(val signMode: SignMode) : LoginNavigation()
object OpenSsoLoginFallback : LoginNavigation()
object GoBack : LoginNavigation()
}

View file

@ -77,6 +77,10 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
loginSharedActionViewModel.post(LoginNavigation.OnServerSelectionDone)
}
override fun resetViewModel() {
viewModel.handle(LoginAction.ResetHomeServerType)
}
override fun invalidate() = withState(viewModel) {
updateSelectedChoice(it.serverType)
}

View file

@ -16,14 +16,16 @@
package im.vector.riotx.features.login
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.core.view.isVisible
import butterknife.OnClick
import com.airbnb.mvrx.withState
import com.airbnb.mvrx.*
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.riotx.R
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.utils.openUrlInExternalBrowser
import kotlinx.android.synthetic.main.fragment_login_server_url_form.*
import javax.inject.Inject
@ -31,7 +33,9 @@ import javax.inject.Inject
/**
*
*/
class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment() {
class LoginServerUrlFormFragment @Inject constructor(
private val errorFormatter: ErrorFormatter
) : AbstractLoginFragment() {
override fun getLayoutResId() = R.layout.fragment_login_server_url_form
@ -64,10 +68,29 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
openUrlInExternalBrowser(requireActivity(), "https://example.org")
}
override fun resetViewModel() {
viewModel.handle(LoginAction.ResetHomeServerUrl)
}
@SuppressLint("SetTextI18n")
@OnClick(R.id.loginServerUrlFormSubmit)
fun submit() {
// TODO Static check of homeserver url, empty, malformed, etc.
viewModel.handle(LoginAction.InitWith(LoginConfig(loginServerUrlFormHomeServerUrl.text.toString(), null)))
// 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 ->
@ -90,5 +113,25 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
}
else -> error("This fragment should not be display in matrix.org mode")
}
when (state.asyncHomeServerLoginFlowRequest) {
is Uninitialized -> {
progressBar.isVisible = false
touchArea.isVisible = false
}
is Loading -> {
progressBar.isVisible = true
touchArea.isVisible = true
}
is Fail -> {
progressBar.isVisible = false
touchArea.isVisible = false
// TODO Error text is not correct
loginServerUrlFormHomeServerUrlTil.error = errorFormatter.toHumanReadable(state.asyncHomeServerLoginFlowRequest.error)
}
is Success -> {
// The home server is valid, the next screen will be opened by the Activity
}
}
}
}

View file

@ -64,6 +64,10 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr
loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
}
override fun resetViewModel() {
viewModel.handle(LoginAction.ResetSignMode)
}
override fun invalidate() = withState(viewModel) {
updateViews(it.serverType)
}

View file

@ -31,4 +31,8 @@ class LoginSplashFragment @Inject constructor() : AbstractLoginFragment() {
fun getStarted() {
loginSharedActionViewModel.post(LoginNavigation.OpenServerSelection)
}
override fun resetViewModel() {
// Nothing to do
}
}

View file

@ -44,6 +44,7 @@ import javax.inject.Inject
/**
* Only login is supported for the moment
* TODO Migrate to new flow
*/
class LoginSsoFallbackFragment @Inject constructor() : VectorBaseFragment(), OnBackPressed {

View file

@ -68,6 +68,44 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
is LoginAction.Login -> handleLogin(action)
is LoginAction.SsoLoginSuccess -> handleSsoLoginSuccess(action)
is LoginAction.ResetAction -> handleResetAction(action)
}
}
private fun handleResetAction(action: LoginAction.ResetAction) {
// Cancel any request
currentTask?.cancel()
currentTask = null
when (action) {
LoginAction.ResetLogin -> {
setState {
copy(
asyncLoginAction = Uninitialized
)
}
}
LoginAction.ResetHomeServerUrl -> {
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized
)
}
}
LoginAction.ResetHomeServerType -> {
setState {
copy(
serverType = ServerType.MatrixOrg
)
}
}
LoginAction.ResetSignMode -> {
setState {
copy(
signMode = SignMode.Unknown
)
}
}
}
}
@ -107,7 +145,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
)
}
authenticator.authenticate(homeServerConnectionConfigFinal, action.login, action.password, object : MatrixCallback<Session> {
currentTask = authenticator.authenticate(homeServerConnectionConfigFinal, action.login, action.password, object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
}
@ -153,7 +191,6 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
}
private fun handleUpdateHomeserver(action: LoginAction.UpdateHomeServer) = withState { state ->
var newConfig: HomeServerConnectionConfig? = null
Try {
val homeServerUri = action.homeServerUrl
@ -167,6 +204,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
&& state.asyncHomeServerLoginFlowRequest is Success) return@withState
currentTask?.cancel()
currentTask = null
homeServerConnectionConfig = newConfig
val homeServerConnectionConfigFinal = homeServerConnectionConfig

View file

@ -22,7 +22,7 @@ import com.airbnb.mvrx.Uninitialized
data class LoginViewState(
val serverType: ServerType = ServerType.MatrixOrg,
val signMode: SignMode = SignMode.SignUp,
val signMode: SignMode = SignMode.Unknown,
val asyncLoginAction: Async<Unit> = Uninitialized,
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized
) : MvRxState

View file

@ -17,6 +17,7 @@
package im.vector.riotx.features.login
enum class SignMode {
Unknown,
// Account creation
SignUp,
// Login