From 6811d31a6d39f3232d9ee205eb0791b49d2708ac Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Dec 2019 20:24:46 +0100 Subject: [PATCH] Soft Logout - request homeserver login flow --- .../vector/riotx/core/error/ErrorFormatter.kt | 1 + .../features/signout/SoftLogoutAction.kt | 1 + .../features/signout/SoftLogoutFragment.kt | 65 +++++--- .../features/signout/SoftLogoutViewModel.kt | 81 ++++++++- .../features/signout/SoftLogoutViewState.kt | 2 + .../main/res/layout/fragment_soft_logout.xml | 154 +++++++++++------- .../src/main/res/layout/item_error_retry.xml | 1 - 7 files changed, 226 insertions(+), 79 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index d4a2160ed1..e01c2b0542 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -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) diff --git a/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutAction.kt b/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutAction.kt index da1d4a3adb..a7f25dddba 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutAction.kt @@ -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... } diff --git a/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutFragment.kt index 1380b9cc5f..52ac4bb8e4 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutFragment.kt @@ -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) { diff --git a/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutViewModel.kt index 2ef69ceffb..8e73da0416 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutViewModel.kt @@ -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(initialState) { + private val activeSessionHolder: ActiveSessionHolder, + private val authenticationService: AuthenticationService +) : VectorViewModel(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 { + 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() // val viewEvents: DataSource = _viewEvents override fun handle(action: SoftLogoutAction) { when (action) { - is SoftLogoutAction.SignInAgain -> handleSignInAgain(action) + is SoftLogoutAction.RetryLoginFlow -> getSupportedLoginFlow() + is SoftLogoutAction.SignInAgain -> handleSignInAgain(action) } } diff --git a/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutViewState.kt b/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutViewState.kt index 87b6a420e1..c58eec821d 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/SoftLogoutViewState.kt @@ -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 = Uninitialized, val asyncLoginAction: Async = Uninitialized, val homeServerUrl: String, val userId: String, diff --git a/vector/src/main/res/layout/fragment_soft_logout.xml b/vector/src/main/res/layout/fragment_soft_logout.xml index 0b4727831d..aece715cd6 100644 --- a/vector/src/main/res/layout/fragment_soft_logout.xml +++ b/vector/src/main/res/layout/fragment_soft_logout.xml @@ -55,73 +55,117 @@ tools:visibility="visible" /> - - - - - - - - - - - - - + + android:layout_gravity="center_horizontal" /> + + android:layout_gravity="center_horizontal" + android:text="@string/login_signin_sso" + android:visibility="gone" + tools:visibility="visible" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +