Soft Logout - request homeserver login flow

This commit is contained in:
Benoit Marty 2019-12-12 20:24:46 +01:00
parent a464c910f8
commit 6811d31a6d
7 changed files with 226 additions and 79 deletions

View File

@ -41,6 +41,7 @@ class ErrorFormatter @Inject constructor(private val stringProvider: StringProvi
stringProvider.getString(R.string.error_network_timeout)
throwable.ioException is UnknownHostException ->
// Invalid homeserver?
// TODO Check network state, airplane mode, etc.
stringProvider.getString(R.string.login_error_unknown_host)
else ->
stringProvider.getString(R.string.error_no_network)

View File

@ -19,6 +19,7 @@ package im.vector.riotx.features.signout
import im.vector.riotx.core.platform.VectorViewModelAction
sealed class SoftLogoutAction : VectorViewModelAction {
object RetryLoginFlow : SoftLogoutAction()
data class SignInAgain(val password: String) : SoftLogoutAction()
// TODO Add reset pwd...
}

View File

@ -30,18 +30,22 @@ import im.vector.riotx.R
import im.vector.riotx.core.dialogs.withColoredButton
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.extensions.showPassword
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.MainActivity
import im.vector.riotx.features.MainActivityArgs
import im.vector.riotx.features.login.LoginMode
import io.reactivex.rxkotlin.subscribeBy
import kotlinx.android.synthetic.main.fragment_soft_logout.*
import kotlinx.android.synthetic.main.item_error_retry.*
import javax.inject.Inject
/**
* In this screen:
* - the user is asked to enter a password to sign in again to a homeserver.
* - or to cleanup all the data
* TODO: migrate to Epoxy (along with all the login screen?)
*/
class SoftLogoutFragment @Inject constructor(
private val errorFormatter: ErrorFormatter
@ -67,6 +71,11 @@ class SoftLogoutFragment @Inject constructor(
}
}
@OnClick(R.id.itemErrorRetryButton)
fun retry() {
softLogoutViewModel.handle(SoftLogoutAction.RetryLoginFlow)
}
@OnClick(R.id.softLogoutSubmit)
fun submit() {
cleanupUi()
@ -75,8 +84,14 @@ class SoftLogoutFragment @Inject constructor(
softLogoutViewModel.handle(SoftLogoutAction.SignInAgain(password))
}
@OnClick(R.id.softLogoutFormSsoSubmit)
fun ssoSubmit() {
// TODO
}
@OnClick(R.id.softLogoutClearDataSubmit)
fun clearData() = withState(softLogoutViewModel) { state ->
fun clearData() {
withState(softLogoutViewModel) { state ->
cleanupUi()
val messageResId = if (state.hasUnsavedKeys) {
@ -99,6 +114,7 @@ class SoftLogoutFragment @Inject constructor(
.show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
}
}
private fun cleanupUi() {
softLogoutSubmit.hideKeyboard()
@ -114,6 +130,14 @@ class SoftLogoutFragment @Inject constructor(
softLogoutE2eWarningNotice.isVisible = state.hasUnsavedKeys
}
private fun setupForm(state: SoftLogoutViewState) {
softLogoutFormLoading.isVisible = state.asyncHomeServerLoginFlowRequest is Loading
softLogoutFormSsoSubmit.isVisible = state.asyncHomeServerLoginFlowRequest.invoke() == LoginMode.Sso
softLogoutFormPassword.isVisible = state.asyncHomeServerLoginFlowRequest.invoke() == LoginMode.Password
softLogoutFormError.isVisible = state.asyncHomeServerLoginFlowRequest is Fail
itemErrorRetryText.setTextOrHide((state.asyncHomeServerLoginFlowRequest as? Fail)?.error?.let { errorFormatter.toHumanReadable(it) })
}
private fun setupSubmitButton() {
softLogoutPasswordField.textChanges()
.map { it.trim().isNotEmpty() }
@ -156,6 +180,7 @@ class SoftLogoutFragment @Inject constructor(
override fun invalidate() = withState(softLogoutViewModel) { state ->
setupUi(state)
setupForm(state)
setupAutoFill()
when (state.asyncLoginAction) {

View File

@ -20,12 +20,16 @@ import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.hasUnsavedKeys
import im.vector.riotx.core.extensions.toReducedUrl
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.login.LoginMode
/**
*
@ -33,8 +37,9 @@ import im.vector.riotx.core.platform.VectorViewModel
class SoftLogoutViewModel @AssistedInject constructor(
@Assisted initialState: SoftLogoutViewState,
private val session: Session,
private val activeSessionHolder: ActiveSessionHolder)
: VectorViewModel<SoftLogoutViewState, SoftLogoutAction>(initialState) {
private val activeSessionHolder: ActiveSessionHolder,
private val authenticationService: AuthenticationService
) : VectorViewModel<SoftLogoutViewState, SoftLogoutAction>(initialState) {
@AssistedInject.Factory
interface Factory {
@ -63,12 +68,82 @@ class SoftLogoutViewModel @AssistedInject constructor(
private var currentTask: Cancelable? = null
init {
// Get the supported login flow
getSupportedLoginFlow()
}
private fun getSupportedLoginFlow() {
val homeServerConnectionConfig = session.sessionParams.homeServerConnectionConfig
currentTask?.cancel()
currentTask = null
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncHomeServerLoginFlowRequest = Loading()
)
}
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
override fun onFailure(failure: Throwable) {
// TODO _viewEvents.post(LoginViewEvents.Error(failure))
setState {
copy(
asyncHomeServerLoginFlowRequest = Fail(failure)
)
}
}
override fun onSuccess(data: LoginFlowResult) {
when (data) {
is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.SSO } -> LoginMode.Sso
data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.PASSWORD } -> LoginMode.Password
else -> LoginMode.Unsupported
}
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|| loginMode == LoginMode.Unsupported) {
notSupported()
} else {
setState {
copy(
asyncHomeServerLoginFlowRequest = Success(loginMode)
)
}
}
}
is LoginFlowResult.OutdatedHomeserver -> {
notSupported()
}
}
}
private fun notSupported() {
// Should not happen since it's a re-logout
// Notify the UI
// _viewEvents.post(LoginViewEvents.OutdatedHomeserver)
setState {
copy(
asyncHomeServerLoginFlowRequest = Fail(IllegalStateException("Should not happen"))
)
}
}
})
}
// TODO Cleanup
// private val _viewEvents = PublishDataSource<LoginViewEvents>()
// val viewEvents: DataSource<LoginViewEvents> = _viewEvents
override fun handle(action: SoftLogoutAction) {
when (action) {
is SoftLogoutAction.RetryLoginFlow -> getSupportedLoginFlow()
is SoftLogoutAction.SignInAgain -> handleSignInAgain(action)
}
}

View File

@ -20,8 +20,10 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.riotx.features.login.LoginMode
data class SoftLogoutViewState(
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized,
val asyncLoginAction: Async<Unit> = Uninitialized,
val homeServerUrl: String,
val userId: String,

View File

@ -55,11 +55,53 @@
tools:visibility="visible" />
<FrameLayout
android:id="@+id/softLogoutPasswordContainer"
android:id="@+id/softLogoutFormContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<!-- Displayed while loading -->
<ProgressBar
android:id="@+id/softLogoutFormLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
<!-- Displayed for SSO mode -->
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutFormSsoSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/login_signin_sso"
android:visibility="gone"
tools:visibility="visible" />
<!-- Displayed in case of error -->
<FrameLayout
android:id="@+id/softLogoutFormError"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/item_error_retry" />
</FrameLayout>
<!-- Displayed for password mode -->
<LinearLayout
android:id="@+id/softLogoutFormPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<FrameLayout
android:id="@+id/softLogoutPasswordContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/softLogoutPasswordFieldTil"
style="@style/VectorTextInputLayout"
@ -122,6 +164,8 @@
tools:ignore="RelativeOverlap" />
</RelativeLayout>
</LinearLayout>
</FrameLayout>
<TextView
android:layout_width="wrap_content"

View File

@ -2,7 +2,6 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"