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,29 +84,36 @@ 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 ->
cleanupUi()
fun clearData() {
withState(softLogoutViewModel) { state ->
cleanupUi()
val messageResId = if (state.hasUnsavedKeys) {
R.string.soft_logout_clear_data_dialog_content
} else {
R.string.soft_logout_clear_data_dialog_e2e_warning_content
val messageResId = if (state.hasUnsavedKeys) {
R.string.soft_logout_clear_data_dialog_content
} else {
R.string.soft_logout_clear_data_dialog_e2e_warning_content
}
AlertDialog.Builder(requireActivity())
.setTitle(R.string.soft_logout_clear_data_dialog_title)
.setMessage(messageResId)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.soft_logout_clear_data_submit) { _, _ ->
MainActivity.restartApp(requireActivity(), MainActivityArgs(
clearCache = true,
clearCredentials = true,
isUserLoggedOut = true
))
}
.show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
}
AlertDialog.Builder(requireActivity())
.setTitle(R.string.soft_logout_clear_data_dialog_title)
.setMessage(messageResId)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.soft_logout_clear_data_submit) { _, _ ->
MainActivity.restartApp(requireActivity(), MainActivityArgs(
clearCache = true,
clearCredentials = true,
isUserLoggedOut = true
))
}
.show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
}
private fun cleanupUi() {
@ -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,13 +68,83 @@ 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.SignInAgain -> handleSignInAgain(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,73 +55,117 @@
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">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/softLogoutPasswordFieldTil"
style="@style/VectorTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/soft_logout_signin_password_hint"
app:errorEnabled="true"
app:errorIconDrawable="@null">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/softLogoutPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword"
android:maxLines="1"
android:paddingEnd="48dp"
android:paddingRight="48dp"
tools:ignore="RtlSymmetry" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/softLogoutPasswordReveal"
android:layout_width="@dimen/layout_touch_size"
android:layout_height="@dimen/layout_touch_size"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:background="?attr/selectableItemBackground"
android:scaleType="center"
android:src="@drawable/ic_eye_black"
android:tint="?attr/colorAccent"
tools:contentDescription="@string/a11y_show_password" />
</FrameLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutForgetPasswordButton"
style="@style/Style.Vector.Login.Button.Text"
<!-- Displayed while loading -->
<ProgressBar
android:id="@+id/softLogoutFormLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="@string/auth_forgot_password" />
android:layout_gravity="center_horizontal" />
<!-- Displayed for SSO mode -->
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutSubmit"
android:id="@+id/softLogoutFormSsoSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:text="@string/soft_logout_signin_submit"
tools:enabled="false"
tools:ignore="RelativeOverlap" />
android:layout_gravity="center_horizontal"
android:text="@string/login_signin_sso"
android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout>
<!-- 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/soft_logout_signin_password_hint"
app:errorEnabled="true"
app:errorIconDrawable="@null">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/softLogoutPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword"
android:maxLines="1"
android:paddingEnd="48dp"
android:paddingRight="48dp"
tools:ignore="RtlSymmetry" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/softLogoutPasswordReveal"
android:layout_width="@dimen/layout_touch_size"
android:layout_height="@dimen/layout_touch_size"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:background="?attr/selectableItemBackground"
android:scaleType="center"
android:src="@drawable/ic_eye_black"
android:tint="?attr/colorAccent"
tools:contentDescription="@string/a11y_show_password" />
</FrameLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutForgetPasswordButton"
style="@style/Style.Vector.Login.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="@string/auth_forgot_password" />
<com.google.android.material.button.MaterialButton
android:id="@+id/softLogoutSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:text="@string/soft_logout_signin_submit"
tools:enabled="false"
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"