From 19261ab2d00dbbd058ce93ae857221722cb4dd07 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 11 Aug 2022 16:41:49 +0100 Subject: [PATCH] removing login2 classes --- .../im/vector/app/config/OnboardingVariant.kt | 1 - .../im/vector/app/core/di/FragmentModule.kt | 108 --- .../app/core/di/MavericksViewModelModule.kt | 12 - .../home/room/list/RoomListViewModel.kt | 22 +- .../features/login2/AbstractLoginFragment2.kt | 165 ---- .../login2/AbstractSSOLoginFragment2.kt | 102 --- .../app/features/login2/LoginAction2.kt | 92 -- .../features/login2/LoginCaptchaFragment2.kt | 196 ----- .../login2/LoginFragmentSigninPassword2.kt | 163 ---- .../login2/LoginFragmentSigninUsername2.kt | 110 --- .../login2/LoginFragmentSignupPassword2.kt | 115 --- .../login2/LoginFragmentSignupUsername2.kt | 139 --- .../features/login2/LoginFragmentToAny2.kt | 202 ----- .../LoginGenericTextInputFormFragment2.kt | 268 ------ .../login2/LoginResetPasswordFragment2.kt | 163 ---- ...nResetPasswordMailConfirmationFragment2.kt | 74 -- .../LoginResetPasswordSuccessFragment2.kt | 48 - .../login2/LoginServerSelectionFragment2.kt | 94 -- .../login2/LoginServerUrlFormFragment2.kt | 150 ---- ...ginSplashSignUpSignInSelectionFragment2.kt | 74 -- .../features/login2/LoginSsoOnlyFragment2.kt | 69 -- .../app/features/login2/LoginViewEvents2.kt | 63 -- .../app/features/login2/LoginViewModel2.kt | 829 ------------------ .../app/features/login2/LoginViewState2.kt | 70 -- .../login2/LoginWaitForEmailFragment2.kt | 75 -- .../app/features/login2/LoginWebFragment2.kt | 262 ------ .../vector/app/features/login2/SignMode2.kt | 27 - .../login2/created/AccountCreatedAction.kt | 25 - .../login2/created/AccountCreatedFragment.kt | 171 ---- .../created/AccountCreatedViewEvents.kt | 27 - .../login2/created/AccountCreatedViewModel.kt | 108 --- .../login2/created/AccountCreatedViewState.kt | 29 - .../login2/terms/LoginTermsFragment2.kt | 119 --- .../features/navigation/DefaultNavigator.kt | 2 - .../app/features/onboarding/Login2Variant.kt | 426 --------- .../features/onboarding/OnboardingActivity.kt | 2 +- .../onboarding/OnboardingVariantFactory.kt | 8 - 37 files changed, 12 insertions(+), 4598 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/SignMode2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt delete mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt delete mode 100755 vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt delete mode 100644 vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt diff --git a/vector-config/src/main/java/im/vector/app/config/OnboardingVariant.kt b/vector-config/src/main/java/im/vector/app/config/OnboardingVariant.kt index ae8cfd1172..8821c8187e 100644 --- a/vector-config/src/main/java/im/vector/app/config/OnboardingVariant.kt +++ b/vector-config/src/main/java/im/vector/app/config/OnboardingVariant.kt @@ -18,6 +18,5 @@ package im.vector.app.config enum class OnboardingVariant { LEGACY, - LOGIN_2, FTUE_AUTH } diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index e86b350534..6b8fe735b3 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -79,24 +79,6 @@ import im.vector.app.features.login.LoginSplashFragment import im.vector.app.features.login.LoginWaitForEmailFragment import im.vector.app.features.login.LoginWebFragment import im.vector.app.features.login.terms.LoginTermsFragment -import im.vector.app.features.login2.LoginCaptchaFragment2 -import im.vector.app.features.login2.LoginFragmentSigninPassword2 -import im.vector.app.features.login2.LoginFragmentSigninUsername2 -import im.vector.app.features.login2.LoginFragmentSignupPassword2 -import im.vector.app.features.login2.LoginFragmentSignupUsername2 -import im.vector.app.features.login2.LoginFragmentToAny2 -import im.vector.app.features.login2.LoginGenericTextInputFormFragment2 -import im.vector.app.features.login2.LoginResetPasswordFragment2 -import im.vector.app.features.login2.LoginResetPasswordMailConfirmationFragment2 -import im.vector.app.features.login2.LoginResetPasswordSuccessFragment2 -import im.vector.app.features.login2.LoginServerSelectionFragment2 -import im.vector.app.features.login2.LoginServerUrlFormFragment2 -import im.vector.app.features.login2.LoginSplashSignUpSignInSelectionFragment2 -import im.vector.app.features.login2.LoginSsoOnlyFragment2 -import im.vector.app.features.login2.LoginWaitForEmailFragment2 -import im.vector.app.features.login2.LoginWebFragment2 -import im.vector.app.features.login2.created.AccountCreatedFragment -import im.vector.app.features.login2.terms.LoginTermsFragment2 import im.vector.app.features.matrixto.MatrixToRoomSpaceFragment import im.vector.app.features.matrixto.MatrixToUserFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment @@ -334,96 +316,6 @@ interface FragmentModule { @FragmentKey(LoginWaitForEmailFragment::class) fun bindLoginWaitForEmailFragment(fragment: LoginWaitForEmailFragment): Fragment - @Binds - @IntoMap - @FragmentKey(LoginFragmentSigninUsername2::class) - fun bindLoginFragmentSigninUsername2(fragment: LoginFragmentSigninUsername2): Fragment - - @Binds - @IntoMap - @FragmentKey(AccountCreatedFragment::class) - fun bindAccountCreatedFragment(fragment: AccountCreatedFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentSignupUsername2::class) - fun bindLoginFragmentSignupUsername2(fragment: LoginFragmentSignupUsername2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentSigninPassword2::class) - fun bindLoginFragmentSigninPassword2(fragment: LoginFragmentSigninPassword2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentSignupPassword2::class) - fun bindLoginFragmentSignupPassword2(fragment: LoginFragmentSignupPassword2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginCaptchaFragment2::class) - fun bindLoginCaptchaFragment2(fragment: LoginCaptchaFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentToAny2::class) - fun bindLoginFragmentToAny2(fragment: LoginFragmentToAny2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginTermsFragment2::class) - fun bindLoginTermsFragment2(fragment: LoginTermsFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginServerUrlFormFragment2::class) - fun bindLoginServerUrlFormFragment2(fragment: LoginServerUrlFormFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordMailConfirmationFragment2::class) - fun bindLoginResetPasswordMailConfirmationFragment2(fragment: LoginResetPasswordMailConfirmationFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordFragment2::class) - fun bindLoginResetPasswordFragment2(fragment: LoginResetPasswordFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordSuccessFragment2::class) - fun bindLoginResetPasswordSuccessFragment2(fragment: LoginResetPasswordSuccessFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginServerSelectionFragment2::class) - fun bindLoginServerSelectionFragment2(fragment: LoginServerSelectionFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginSsoOnlyFragment2::class) - fun bindLoginSsoOnlyFragment2(fragment: LoginSsoOnlyFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginSplashSignUpSignInSelectionFragment2::class) - fun bindLoginSplashSignUpSignInSelectionFragment2(fragment: LoginSplashSignUpSignInSelectionFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginWebFragment2::class) - fun bindLoginWebFragment2(fragment: LoginWebFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginGenericTextInputFormFragment2::class) - fun bindLoginGenericTextInputFormFragment2(fragment: LoginGenericTextInputFormFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginWaitForEmailFragment2::class) - fun bindLoginWaitForEmailFragment2(fragment: LoginWaitForEmailFragment2): Fragment - @Binds @IntoMap @FragmentKey(FtueAuthLegacyStyleCaptchaFragment::class) diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 331b4afa18..6da47c4f7d 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -58,8 +58,6 @@ import im.vector.app.features.location.LocationSharingViewModel import im.vector.app.features.location.live.map.LiveLocationMapViewModel import im.vector.app.features.location.preview.LocationPreviewViewModel import im.vector.app.features.login.LoginViewModel -import im.vector.app.features.login2.LoginViewModel2 -import im.vector.app.features.login2.created.AccountCreatedViewModel import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel import im.vector.app.features.media.VectorAttachmentViewerViewModel import im.vector.app.features.onboarding.OnboardingViewModel @@ -456,21 +454,11 @@ interface MavericksViewModelModule { @MavericksViewModelKey(MatrixToBottomSheetViewModel::class) fun matrixToBottomSheetViewModelFactory(factory: MatrixToBottomSheetViewModel.Factory): MavericksAssistedViewModelFactory<*, *> - @Binds - @IntoMap - @MavericksViewModelKey(AccountCreatedViewModel::class) - fun accountCreatedViewModelFactory(factory: AccountCreatedViewModel.Factory): MavericksAssistedViewModelFactory<*, *> - @Binds @IntoMap @MavericksViewModelKey(OnboardingViewModel::class) fun onboardingViewModelFactory(factory: OnboardingViewModel.Factory): MavericksAssistedViewModelFactory<*, *> - @Binds - @IntoMap - @MavericksViewModelKey(LoginViewModel2::class) - fun loginViewModel2Factory(factory: LoginViewModel2.Factory): MavericksAssistedViewModelFactory<*, *> - @Binds @IntoMap @MavericksViewModelKey(LoginViewModel::class) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 4b76daf502..2e0a6ae347 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -146,17 +146,17 @@ class RoomListViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() private val roomListSectionBuilder = RoomListSectionBuilder( - session, - stringProvider, - spaceStateHandler, - viewModelScope, - autoAcceptInvites, - { - updatableQuery = it - }, - suggestedRoomJoiningState, - !vectorPreferences.prefSpacesShowAllRoomInHome() - ) + session, + stringProvider, + spaceStateHandler, + viewModelScope, + autoAcceptInvites, + { + updatableQuery = it + }, + suggestedRoomJoiningState, + !vectorPreferences.prefSpacesShowAllRoomInHome() + ) val sections: List by lazy { roomListSectionBuilder.buildSections(initialState.displayMode) diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt deleted file mode 100644 index aae51fa959..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Bundle -import android.view.View -import androidx.annotation.CallSuper -import androidx.transition.TransitionInflater -import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.activityViewModel -import com.airbnb.mvrx.withState -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.dialogs.UnrecognizedCertificateDialog -import im.vector.app.core.platform.OnBackPressed -import im.vector.app.core.platform.VectorBaseFragment -import kotlinx.coroutines.CancellationException -import org.matrix.android.sdk.api.failure.Failure - -/** - * Parent Fragment for all the login/registration screens. - */ -abstract class AbstractLoginFragment2 : VectorBaseFragment(), OnBackPressed { - - protected val loginViewModel: LoginViewModel2 by activityViewModel() - - private var isResetPasswordStarted = false - - // Due to async, we keep a boolean to avoid displaying twice the cancellation dialog - private var displayCancelDialog = true - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - context?.let { - sharedElementEnterTransition = TransitionInflater.from(it).inflateTransition(android.R.transition.move) - } - } - - @CallSuper - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - loginViewModel.observeViewEvents { - handleLoginViewEvents(it) - } - } - - private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents2) { - when (loginViewEvents) { - is LoginViewEvents2.Failure -> showFailure(loginViewEvents.throwable) - else -> - // This is handled by the Activity - Unit - } - } - - override fun showFailure(throwable: Throwable) { - // Only the resumed Fragment can eventually show the error, to avoid multiple dialog display - if (!isResumed) { - return - } - - when (throwable) { - is CancellationException -> - /* Ignore this error, user has cancelled the action */ - Unit - is Failure.UnrecognizedCertificateFailure -> - showUnrecognizedCertificateFailure(throwable) - else -> - onError(throwable) - } - } - - private fun showUnrecognizedCertificateFailure(failure: Failure.UnrecognizedCertificateFailure) { - // Ask the user to accept the certificate - unrecognizedCertificateDialog.show(requireActivity(), - failure.fingerprint, - failure.url, - object : UnrecognizedCertificateDialog.Callback { - override fun onAccept() { - // User accept the certificate - loginViewModel.handle(LoginAction2.UserAcceptCertificate(failure.fingerprint)) - } - - override fun onIgnore() { - // Cannot happen in this case - } - - override fun onReject() { - // Nothing to do in this case - } - }) - } - - open fun onError(throwable: Throwable) { - super.showFailure(throwable) - } - - override fun onBackPressed(toolbarButton: Boolean): Boolean { - return when { - displayCancelDialog && loginViewModel.isRegistrationStarted -> { - // Ask for confirmation before cancelling the registration - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.login_signup_cancel_confirmation_title) - .setMessage(R.string.login_signup_cancel_confirmation_content) - .setPositiveButton(R.string.yes) { _, _ -> - displayCancelDialog = false - vectorBaseActivity.onBackPressed() - } - .setNegativeButton(R.string.no, null) - .show() - - true - } - displayCancelDialog && isResetPasswordStarted -> { - // Ask for confirmation before cancelling the reset password - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.login_reset_password_cancel_confirmation_title) - .setMessage(R.string.login_reset_password_cancel_confirmation_content) - .setPositiveButton(R.string.yes) { _, _ -> - displayCancelDialog = false - vectorBaseActivity.onBackPressed() - } - .setNegativeButton(R.string.no, null) - .show() - - true - } - else -> { - resetViewModel() - // Do not consume the Back event - false - } - } - } - - final override fun invalidate() = withState(loginViewModel) { state -> - // True when email is sent with success to the homeserver - isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not() - - updateWithState(state) - } - - open fun updateWithState(state: LoginViewState2) { - // No op by default - } - - // Reset any modification on the loginViewModel by the current fragment - abstract fun resetViewModel() -} diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt deleted file mode 100644 index 8bc531b25d..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.content.ComponentName -import android.net.Uri -import androidx.browser.customtabs.CustomTabsClient -import androidx.browser.customtabs.CustomTabsServiceConnection -import androidx.browser.customtabs.CustomTabsSession -import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.withState -import im.vector.app.core.utils.openUrlInChromeCustomTab -import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.hasSso -import im.vector.app.features.login.ssoIdentityProviders - -abstract class AbstractSSOLoginFragment2 : AbstractLoginFragment2() { - - // For sso - private var customTabsServiceConnection: CustomTabsServiceConnection? = null - private var customTabsClient: CustomTabsClient? = null - private var customTabsSession: CustomTabsSession? = null - - override fun onStart() { - super.onStart() - val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() } - if (hasSSO) { - val packageName = CustomTabsClient.getPackageName(requireContext(), null) - - // packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device - if (packageName != null) { - customTabsServiceConnection = object : CustomTabsServiceConnection() { - override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { - customTabsClient = client - .also { it.warmup(0L) } - prefetchIfNeeded() - } - - override fun onServiceDisconnected(name: ComponentName?) { - } - } - .also { - CustomTabsClient.bindCustomTabsService( - requireContext(), - // Despite the API, packageName cannot be null - packageName, - it - ) - } - } - } - } - - override fun onStop() { - super.onStop() - val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() } - if (hasSSO) { - customTabsServiceConnection?.let { requireContext().unbindService(it) } - customTabsServiceConnection = null - } - } - - private fun prefetchUrl(url: String) { - if (customTabsSession == null) { - customTabsSession = customTabsClient?.newSession(null) - } - - customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null) - } - - protected fun openInCustomTab(ssoUrl: String) { - openUrlInChromeCustomTab(requireContext(), customTabsSession, ssoUrl) - } - - private fun prefetchIfNeeded() { - withState(loginViewModel) { state -> - if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) { - // in this case we can prefetch (not other cases for privacy concerns) - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = null - ) - ?.let { prefetchUrl(it) } - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt deleted file mode 100644 index 37dafbfbe0..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import im.vector.app.core.platform.VectorViewModelAction -import im.vector.app.features.login.LoginConfig -import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import org.matrix.android.sdk.api.auth.registration.RegisterThreePid -import org.matrix.android.sdk.api.network.ssl.Fingerprint - -sealed class LoginAction2 : VectorViewModelAction { - // First action - data class UpdateSignMode(val signMode: SignMode2) : LoginAction2() - - // Signin, but user wants to choose a server - object ChooseAServerForSignin : LoginAction2() - - object EnterServerUrl : LoginAction2() - object ChooseDefaultHomeServer : LoginAction2() - data class UpdateHomeServer(val homeServerUrl: String) : LoginAction2() - data class LoginWithToken(val loginToken: String) : LoginAction2() - data class WebLoginSuccess(val credentials: Credentials) : LoginAction2() - data class InitWith(val loginConfig: LoginConfig?) : LoginAction2() - data class ResetPassword(val email: String, val newPassword: String) : LoginAction2() - object ResetPasswordMailConfirmed : LoginAction2() - - // Username to Login or Register, depending on the signMode - data class SetUserName(val username: String) : LoginAction2() - - // Password to Login or Register, depending on the signMode - data class SetUserPassword(val password: String) : LoginAction2() - - // When user has selected a homeserver - data class LoginWith(val login: String, val password: String) : LoginAction2() - - // Register actions - open class RegisterAction : LoginAction2() - - data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction() - object SendAgainThreePid : RegisterAction() - - // TODO Confirm Email (from link in the email, open in the phone, intercepted by the app) - data class ValidateThreePid(val code: String) : RegisterAction() - - data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction() - object StopEmailValidationCheck : RegisterAction() - - data class CaptchaDone(val captchaResponse: String) : RegisterAction() - object AcceptTerms : RegisterAction() - object RegisterDummy : RegisterAction() - - // Reset actions - open class ResetAction : LoginAction2() - - object ResetHomeServerUrl : ResetAction() - object ResetSignMode : ResetAction() - object ResetSignin : ResetAction() - object ResetSignup : ResetAction() - object ResetResetPassword : ResetAction() - - // Homeserver history - object ClearHomeServerHistory : LoginAction2() - - // For the soft logout case - data class SetupSsoForSessionRecovery( - val homeServerUrl: String, - val deviceId: String, - val ssoIdentityProviders: List? - ) : LoginAction2() - - data class PostViewEvent(val viewEvent: LoginViewEvents2) : LoginAction2() - - data class UserAcceptCertificate(val fingerprint: Fingerprint) : LoginAction2() - - // Account customization is over - object Finish : LoginAction2() -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt deleted file mode 100644 index 5fabe0ca32..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.annotation.SuppressLint -import android.content.DialogInterface -import android.graphics.Bitmap -import android.net.http.SslError -import android.os.Build -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.ViewGroup -import android.webkit.SslErrorHandler -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView -import android.webkit.WebViewClient -import androidx.core.view.isVisible -import com.airbnb.mvrx.args -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.utils.AssetReader -import im.vector.app.databinding.FragmentLoginCaptchaBinding -import im.vector.app.features.login.JavascriptResponse -import im.vector.app.features.login.LoginCaptchaFragmentArgument -import org.matrix.android.sdk.api.util.MatrixJsonParser -import timber.log.Timber -import java.net.URLDecoder -import java.util.Formatter -import javax.inject.Inject - -/** - * In this screen, the user is asked to confirm he is not a robot. - */ -class LoginCaptchaFragment2 @Inject constructor( - private val assetReader: AssetReader -) : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginCaptchaBinding { - return FragmentLoginCaptchaBinding.inflate(inflater, container, false) - } - - private val params: LoginCaptchaFragmentArgument by args() - - private var isWebViewLoaded = false - - @SuppressLint("SetJavaScriptEnabled") - private fun setupWebView(state: LoginViewState2) { - views.loginCaptchaWevView.settings.javaScriptEnabled = true - - val reCaptchaPage = assetReader.readAssetFile("reCaptchaPage.html") ?: error("missing asset reCaptchaPage.html") - - val html = Formatter().format(reCaptchaPage, params.siteKey).toString() - val mime = "text/html" - val encoding = "utf-8" - - val homeServerUrl = state.homeServerUrl ?: error("missing url of homeserver") - views.loginCaptchaWevView.loadDataWithBaseURL(homeServerUrl, html, mime, encoding, null) - views.loginCaptchaWevView.requestLayout() - - views.loginCaptchaWevView.webViewClient = object : WebViewClient() { - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - - if (!isAdded) { - return - } - - // Show loader - views.loginCaptchaProgress.isVisible = true - } - - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - - if (!isAdded) { - return - } - - // Hide loader - views.loginCaptchaProgress.isVisible = false - } - - override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { - Timber.d("## onReceivedSslError() : ${error.certificate}") - - if (!isAdded) { - return - } - - MaterialAlertDialogBuilder(requireActivity()) - .setMessage(R.string.ssl_could_not_verify) - .setPositiveButton(R.string.ssl_trust) { _, _ -> - Timber.d("## onReceivedSslError() : the user trusted") - handler.proceed() - } - .setNegativeButton(R.string.ssl_do_not_trust) { _, _ -> - Timber.d("## onReceivedSslError() : the user did not trust") - handler.cancel() - } - .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - handler.cancel() - Timber.d("## onReceivedSslError() : the user dismisses the trust dialog.") - dialog.dismiss() - return@OnKeyListener true - } - false - }) - .setCancelable(false) - .show() - } - - // common error message - private fun onError(errorMessage: String) { - Timber.e("## onError() : $errorMessage") - - // TODO - // Toast.makeText(this@AccountCreationCaptchaActivity, errorMessage, Toast.LENGTH_LONG).show() - - // on error case, close this activity - // runOnUiThread(Runnable { finish() }) - } - - @SuppressLint("NewApi") - override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { - super.onReceivedHttpError(view, request, errorResponse) - - if (request.url.toString().endsWith("favicon.ico")) { - // Ignore this error - return - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - onError(errorResponse.reasonPhrase) - } else { - onError(errorResponse.toString()) - } - } - - @Deprecated("Deprecated in Java") - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { - @Suppress("DEPRECATION") - super.onReceivedError(view, errorCode, description, failingUrl) - onError(description) - } - - @Deprecated("Deprecated in Java") - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - if (url?.startsWith("js:") == true) { - var json = url.substring(3) - var javascriptResponse: JavascriptResponse? = null - - try { - // URL decode - json = URLDecoder.decode(json, "UTF-8") - javascriptResponse = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java).fromJson(json) - } catch (e: Exception) { - Timber.e(e, "## shouldOverrideUrlLoading(): failed") - } - - val response = javascriptResponse?.response - if (javascriptResponse?.action == "verifyCallback" && response != null) { - loginViewModel.handle(LoginAction2.CaptchaDone(response)) - } - } - return true - } - } - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignup) - } - - override fun updateWithState(state: LoginViewState2) { - if (!isWebViewLoaded) { - setupWebView(state) - isWebViewLoaded = true - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt deleted file mode 100644 index 34bebd655a..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.hidePassword -import im.vector.app.databinding.FragmentLoginSigninPassword2Binding -import im.vector.app.features.home.AvatarRenderer -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.login.LoginProfileInfo -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.isInvalidPassword -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject -import javax.net.ssl.HttpsURLConnection - -/** - * In this screen: - * - the user is asked for password to sign in to a homeserver. - * - He also can reset his password - */ -class LoginFragmentSigninPassword2 @Inject constructor( - private val avatarRenderer: AvatarRenderer -) : AbstractSSOLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninPassword2Binding { - return FragmentLoginSigninPassword2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupForgottenPasswordButton() - setupAutoFill() - - views.passwordField.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupForgottenPasswordButton() { - views.forgetPasswordButton.setOnClickListener { forgetPasswordClicked() } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD) - } - } - - private fun submit() { - cleanupUi() - - val password = views.passwordField.text.toString() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (password.isEmpty()) { - views.passwordFieldTil.error = getString(R.string.error_empty_field_your_password) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.SetUserPassword(password)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.passwordFieldTil.error = null - } - - private fun setupUi(state: LoginViewState2) { - // Name and avatar - views.loginWelcomeBack.text = getString( - R.string.login_welcome_back, - state.loginProfileInfo()?.displayName?.takeIf { it.isNotBlank() } ?: state.userIdentifier() - ) - - avatarRenderer.render( - profileInfo = state.loginProfileInfo() ?: LoginProfileInfo(state.userIdentifier(), null, null), - imageView = views.loginUserIcon - ) - - views.loginWelcomeBackWarning.isVisible = ((state.loginProfileInfo as? Fail) - ?.error as? Failure.ServerError) - ?.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */ - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - views.passwordField - .textChanges() - .map { it.isNotEmpty() } - .onEach { - views.passwordFieldTil.error = null - views.loginSubmit.isEnabled = it - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun forgetPasswordClicked() { - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OpenResetPasswordScreen)) - } - - override fun resetViewModel() { - // loginViewModel.handle(LoginAction2.ResetSignin) - } - - override fun onError(throwable: Throwable) { - if (throwable.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - - if (state.isLoading) { - // Ensure password is hidden - views.passwordField.hidePassword() - } - } - - /** - * Detect if password ends or starts with spaces. - */ - private fun spaceInPassword() = views.passwordField.text.toString().let { it.trim() != it } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt deleted file mode 100644 index cb346451de..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.autofill.HintConstants -import androidx.lifecycle.lifecycleScope -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.databinding.FragmentLoginSigninUsername2Binding -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen: - * - the user is asked for its matrix ID, and have the possibility to open the screen to select a server. - */ -class LoginFragmentSigninUsername2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninUsername2Binding { - return FragmentLoginSigninUsername2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupAutoFill() - views.loginChooseAServer.setOnClickListener { - loginViewModel.handle(LoginAction2.ChooseAServerForSignin) - } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) - } - } - - private fun submit() { - cleanupUi() - - val login = views.loginField.text.toString() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (login.isEmpty()) { - views.loginFieldTil.error = getString(R.string.error_empty_field_enter_user_name) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.SetUserName(login)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.loginFieldTil.error = null - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - views.loginField.textChanges() - .map { it.trim().isNotEmpty() } - .onEach { - views.loginFieldTil.error = null - views.loginSubmit.isEnabled = it - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignin) - } - - override fun onError(throwable: Throwable) { - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt deleted file mode 100644 index 806ff0524b..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.lifecycle.lifecycleScope -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.hidePassword -import im.vector.app.databinding.FragmentLoginSignupPassword2Binding -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen: - * - the user is asked to choose a password to sign up to a homeserver. - */ -class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSignupPassword2Binding { - return FragmentLoginSignupPassword2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupAutoFill() - - views.passwordField.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) - } - } - - private fun submit() { - cleanupUi() - - val password = views.passwordField.text.toString() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (password.isEmpty()) { - views.passwordFieldTil.error = getString(R.string.error_empty_field_choose_password) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.SetUserPassword(password)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.passwordFieldTil.error = null - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - views.passwordField.textChanges() - .onEach { password -> - views.passwordFieldTil.error = null - views.loginSubmit.isEnabled = password.isNotEmpty() - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - override fun resetViewModel() { - // loginViewModel.handle(LoginAction2.ResetSignup) - } - - override fun onError(throwable: Throwable) { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - - override fun updateWithState(state: LoginViewState2) { - views.loginMatrixIdentifier.text = state.userIdentifier() - - if (state.isLoading) { - // Ensure password is hidden - views.passwordField.hidePassword() - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt deleted file mode 100644 index a7c4b25344..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.annotation.SuppressLint -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.databinding.FragmentLoginSignupUsername2Binding -import im.vector.app.features.login.LoginMode -import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.SocialLoginButtonsView -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen: - * - the user is asked for an identifier to sign up to a homeserver. - * - SSO option are displayed if available - */ -class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSignupUsername2Binding { - return FragmentLoginSignupUsername2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupAutoFill() - setupSocialLoginButtons() - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) - } - } - - private fun setupSocialLoginButtons() { - views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_UP - } - - private fun submit() { - cleanupUi() - - val login = views.loginField.text.toString().trim() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (login.isEmpty()) { - views.loginFieldTil.error = getString(R.string.error_empty_field_choose_user_name) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.SetUserName(login)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.loginFieldTil.error = null - } - - private fun setupUi(state: LoginViewState2) { - views.loginSubtitle.text = getString(R.string.login_signup_to, state.homeServerUrlFromUser.toReducedUrl()) - - if (state.loginMode is LoginMode.SsoAndPassword) { - views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() - views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = provider?.id - ) - ?.let { openInCustomTab(it) } - } - } - } else { - views.loginSocialLoginContainer.isVisible = false - views.loginSocialLoginButtons.ssoIdentityProviders = null - } - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - views.loginField.textChanges() - .map { it.trim() } - .onEach { text -> - val isNotEmpty = text.isNotEmpty() - views.loginFieldTil.error = null - views.loginSubmit.isEnabled = isNotEmpty - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - override fun resetViewModel() { - // loginViewModel.handle(LoginAction2.ResetSignup) - } - - override fun onError(throwable: Throwable) { - views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - - @SuppressLint("SetTextI18n") - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt deleted file mode 100644 index cc143b9255..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.hidePassword -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.databinding.FragmentLoginSigninToAny2Binding -import im.vector.app.features.login.LoginMode -import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.SocialLoginButtonsView -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import org.matrix.android.sdk.api.failure.isInvalidPassword -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen: - * User want to sign in and has selected a server to do so - * - the user is asked for login (or email) and password to sign in to a homeserver. - * - He also can reset his password - * - It also possible to use SSO if server support it in this screen - */ -class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninToAny2Binding { - return FragmentLoginSigninToAny2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupForgottenPasswordButton() - setupAutoFill() - setupSocialLoginButtons() - - views.passwordField.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupForgottenPasswordButton() { - views.forgetPasswordButton.setOnClickListener { forgetPasswordClicked() } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) - views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD) - } - } - - private fun setupSocialLoginButtons() { - views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_IN - } - - private fun submit() { - cleanupUi() - - val login = views.loginField.text.toString() - val password = views.passwordField.text.toString() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (login.isEmpty()) { - views.loginFieldTil.error = getString(R.string.error_empty_field_enter_user_name) - error++ - } - if (password.isEmpty()) { - views.passwordFieldTil.error = getString(R.string.error_empty_field_your_password) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.LoginWith(login, password)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.loginFieldTil.error = null - views.passwordFieldTil.error = null - } - - private fun setupUi(state: LoginViewState2) { - views.loginTitle.text = getString(R.string.login_connect_to, state.homeServerUrlFromUser.toReducedUrl()) - - if (state.loginMode is LoginMode.SsoAndPassword) { - views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() - views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = provider?.id - ) - ?.let { openInCustomTab(it) } - } - } - } else { - views.loginSocialLoginContainer.isVisible = false - views.loginSocialLoginButtons.ssoIdentityProviders = null - } - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - combine( - views.loginField.textChanges().map { it.trim().isNotEmpty() }, - views.passwordField.textChanges().map { it.isNotEmpty() } - ) { isLoginNotEmpty, isPasswordNotEmpty -> - isLoginNotEmpty && isPasswordNotEmpty - } - .onEach { - views.loginFieldTil.error = null - views.passwordFieldTil.error = null - views.loginSubmit.isEnabled = it - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun forgetPasswordClicked() { - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OpenResetPasswordScreen)) - } - - override fun resetViewModel() { - // loginViewModel.handle(LoginAction2.ResetSignin) - } - - override fun onError(throwable: Throwable) { - // Show M_WEAK_PASSWORD error in the password field - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_WEAK_PASSWORD) { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } else { - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - // Trick to display the error without text. - views.loginFieldTil.error = " " - if (throwable.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - } - } - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - - if (state.isLoading) { - // Ensure password is hidden - views.passwordField.hidePassword() - } - } - - /** - * Detect if password ends or starts with spaces. - */ - private fun spaceInPassword() = views.passwordField.text.toString().let { it.trim() != it } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt deleted file mode 100644 index 91954f29e4..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Build -import android.os.Bundle -import android.text.InputType -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.args -import com.google.i18n.phonenumbers.NumberParseException -import com.google.i18n.phonenumbers.PhoneNumberUtil -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.isEmail -import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.databinding.FragmentLoginGenericTextInputForm2Binding -import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument -import im.vector.app.features.login.TextInputFormFragmentMode -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.registration.RegisterThreePid -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.is401 -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen, the user is asked for a text input. - */ -class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFragment2() { - - private val params: LoginGenericTextInputFormFragmentArgument by args() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginGenericTextInputForm2Binding { - return FragmentLoginGenericTextInputForm2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - setupUi() - setupSubmitButton() - setupTil() - setupAutoFill() - } - - private fun setupViews() { - views.loginGenericTextInputFormOtherButton.setOnClickListener { onOtherButtonClicked() } - views.loginGenericTextInputFormSubmit.setOnClickListener { submit() } - views.loginGenericTextInputFormLater.setOnClickListener { submit() } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.loginGenericTextInputFormTextInput.setAutofillHints( - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS - TextInputFormFragmentMode.SetMsisdn -> HintConstants.AUTOFILL_HINT_PHONE_NUMBER - TextInputFormFragmentMode.ConfirmMsisdn -> HintConstants.AUTOFILL_HINT_SMS_OTP - } - ) - } - } - - private fun setupTil() { - views.loginGenericTextInputFormTextInput.textChanges() - .onEach { - views.loginGenericTextInputFormTil.error = null - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun setupUi() { - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> { - views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_email_title_2) - views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_email_notice_2) - // Text will be updated with the state - views.loginGenericTextInputFormMandatoryNotice.isVisible = params.mandatory - views.loginGenericTextInputFormNotice2.isVisible = false - views.loginGenericTextInputFormTil.hint = - getString(if (params.mandatory) R.string.login_set_email_mandatory_hint else R.string.login_set_email_optional_hint) - views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - views.loginGenericTextInputFormOtherButton.isVisible = false - views.loginGenericTextInputFormSubmit.text = getString(R.string.login_set_email_submit) - } - TextInputFormFragmentMode.SetMsisdn -> { - views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_msisdn_title_2) - views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_msisdn_notice_2) - // Text will be updated with the state - views.loginGenericTextInputFormMandatoryNotice.isVisible = params.mandatory - views.loginGenericTextInputFormNotice2.setTextOrHide(getString(R.string.login_set_msisdn_notice2)) - views.loginGenericTextInputFormTil.hint = - getString(if (params.mandatory) R.string.login_set_msisdn_mandatory_hint else R.string.login_set_msisdn_optional_hint) - views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_PHONE - views.loginGenericTextInputFormOtherButton.isVisible = false - views.loginGenericTextInputFormSubmit.text = getString(R.string.login_set_msisdn_submit) - } - TextInputFormFragmentMode.ConfirmMsisdn -> { - views.loginGenericTextInputFormTitle.text = getString(R.string.login_msisdn_confirm_title) - views.loginGenericTextInputFormNotice.text = getString(R.string.login_msisdn_confirm_notice, params.extra) - views.loginGenericTextInputFormMandatoryNotice.isVisible = false - views.loginGenericTextInputFormNotice2.isVisible = false - views.loginGenericTextInputFormTil.hint = - getString(R.string.login_msisdn_confirm_hint) - views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_NUMBER - views.loginGenericTextInputFormOtherButton.isVisible = true - views.loginGenericTextInputFormOtherButton.text = getString(R.string.login_msisdn_confirm_send_again) - views.loginGenericTextInputFormSubmit.text = getString(R.string.login_msisdn_confirm_submit) - } - } - } - - private fun onOtherButtonClicked() { - when (params.mode) { - TextInputFormFragmentMode.ConfirmMsisdn -> { - loginViewModel.handle(LoginAction2.SendAgainThreePid) - } - else -> { - // Should not happen, button is not displayed - } - } - } - - private fun submit() { - cleanupUi() - val text = views.loginGenericTextInputFormTextInput.text.toString() - - if (text.isEmpty()) { - // Perform dummy action - loginViewModel.handle(LoginAction2.RegisterDummy) - } else { - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> { - loginViewModel.handle(LoginAction2.AddThreePid(RegisterThreePid.Email(text))) - } - TextInputFormFragmentMode.SetMsisdn -> { - getCountryCodeOrShowError(text)?.let { countryCode -> - loginViewModel.handle(LoginAction2.AddThreePid(RegisterThreePid.Msisdn(text, countryCode))) - } - } - TextInputFormFragmentMode.ConfirmMsisdn -> { - loginViewModel.handle(LoginAction2.ValidateThreePid(text)) - } - } - } - } - - private fun cleanupUi() { - views.loginGenericTextInputFormSubmit.hideKeyboard() - views.loginGenericTextInputFormSubmit.error = null - } - - private fun getCountryCodeOrShowError(text: String): String? { - // We expect an international format for the moment (see https://github.com/vector-im/riotX-android/issues/693) - if (text.startsWith("+")) { - try { - val phoneNumber = PhoneNumberUtil.getInstance().parse(text, null) - return PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode) - } catch (e: NumberParseException) { - views.loginGenericTextInputFormTil.error = getString(R.string.login_msisdn_error_other) - } - } else { - views.loginGenericTextInputFormTil.error = getString(R.string.login_msisdn_error_not_international) - } - - // Error - return null - } - - private fun setupSubmitButton() { - views.loginGenericTextInputFormSubmit.isEnabled = false - views.loginGenericTextInputFormTextInput.textChanges() - .onEach { text -> - views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(text) - updateSubmitButtons(text) - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun updateSubmitButtons(text: CharSequence) { - if (params.mandatory) { - views.loginGenericTextInputFormSubmit.isVisible = true - views.loginGenericTextInputFormLater.isVisible = false - } else { - views.loginGenericTextInputFormSubmit.isVisible = text.isNotEmpty() - views.loginGenericTextInputFormLater.isVisible = text.isEmpty() - } - } - - private fun isInputValid(input: CharSequence): Boolean { - return if (input.isEmpty() && !params.mandatory) { - true - } else { - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> input.isEmail() - TextInputFormFragmentMode.SetMsisdn -> input.isNotBlank() - TextInputFormFragmentMode.ConfirmMsisdn -> input.isNotBlank() - } - } - } - - override fun onError(throwable: Throwable) { - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> { - if (throwable.is401()) { - // This is normal use case, we go to the mail waiting screen - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnSendEmailSuccess(loginViewModel.currentThreePid ?: ""))) - } else { - views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) - } - } - TextInputFormFragmentMode.SetMsisdn -> { - if (throwable.is401()) { - // This is normal use case, we go to the enter code screen - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnSendMsisdnSuccess(loginViewModel.currentThreePid ?: ""))) - } else { - views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) - } - } - TextInputFormFragmentMode.ConfirmMsisdn -> { - when { - throwable is Failure.SuccessError -> - // The entered code is not correct - views.loginGenericTextInputFormTil.error = getString(R.string.login_validation_code_is_not_correct) - throwable.is401() -> - // It can happen if user request again the 3pid - Unit - else -> - views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) - } - } - } - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignup) - } - - override fun updateWithState(state: LoginViewState2) { - views.loginGenericTextInputFormMandatoryNotice.text = when (params.mode) { - TextInputFormFragmentMode.SetEmail -> getString(R.string.login_set_email_mandatory_notice_2, state.homeServerUrlFromUser.toReducedUrl()) - TextInputFormFragmentMode.SetMsisdn -> getString(R.string.login_set_msisdn_mandatory_notice_2, state.homeServerUrlFromUser.toReducedUrl()) - TextInputFormFragmentMode.ConfirmMsisdn -> null - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt deleted file mode 100644 index 7916d9bbf2..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.lifecycle.lifecycleScope -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.hidePassword -import im.vector.app.core.extensions.isEmail -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.core.utils.autoResetTextInputLayoutErrors -import im.vector.app.databinding.FragmentLoginResetPassword2Binding -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen, the user is asked for email and new password to reset his password. - */ -class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2() { - - // Show warning only once - private var showWarning = true - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPassword2Binding { - return FragmentLoginResetPassword2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupAutoFill() - - autoResetTextInputLayoutErrors(listOf(views.resetPasswordEmailTil, views.passwordFieldTil)) - - views.passwordField.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.resetPasswordEmail.setAutofillHints(HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS) - views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) - } - } - - private fun setupUi(state: LoginViewState2) { - views.resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrlFromUser.toReducedUrl()) - } - - private fun setupSubmitButton() { - views.resetPasswordSubmit.setOnClickListener { submit() } - combine( - views.resetPasswordEmail.textChanges().map { it.isEmail() }, - views.passwordField.textChanges().map { it.isNotEmpty() } - ) { isEmail, isPasswordNotEmpty -> - isEmail && isPasswordNotEmpty - } - .onEach { - views.resetPasswordSubmit.isEnabled = it - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun submit() { - cleanupUi() - - var error = 0 - - val email = views.resetPasswordEmail.text.toString() - val password = views.passwordField.text.toString() - - if (email.isEmpty()) { - views.resetPasswordEmailTil.error = getString(R.string.auth_reset_password_missing_email) - error++ - } - - if (password.isEmpty()) { - views.passwordFieldTil.error = getString(R.string.login_please_choose_a_new_password) - error++ - } - - if (error > 0) { - return - } - - if (showWarning) { - // Display a warning as Riot-Web does first - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.login_reset_password_warning_title) - .setMessage(R.string.login_reset_password_warning_content) - .setPositiveButton(R.string.login_reset_password_warning_submit) { _, _ -> - showWarning = false - doSubmit() - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } else { - doSubmit() - } - } - - private fun doSubmit() { - val email = views.resetPasswordEmail.text.toString() - val password = views.passwordField.text.toString() - - loginViewModel.handle(LoginAction2.ResetPassword(email, password)) - } - - private fun cleanupUi() { - views.resetPasswordSubmit.hideKeyboard() - views.resetPasswordEmailTil.error = null - views.passwordFieldTil.error = null - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetResetPassword) - } - - override fun onError(throwable: Throwable) { - views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(throwable) - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - - if (state.isLoading) { - // Ensure new password is hidden - views.passwordField.hidePassword() - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt deleted file mode 100644 index de1bcb8eea..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmation2Binding -import org.matrix.android.sdk.api.failure.is401 -import javax.inject.Inject - -/** - * In this screen, the user is asked to check their email and to click on a button once it's done. - */ -class LoginResetPasswordMailConfirmationFragment2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordMailConfirmation2Binding { - return FragmentLoginResetPasswordMailConfirmation2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - views.resetPasswordMailConfirmationSubmit.setOnClickListener { submit() } - } - - private fun setupUi(state: LoginViewState2) { - views.resetPasswordMailConfirmationNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, state.resetPasswordEmail) - } - - private fun submit() { - loginViewModel.handle(LoginAction2.ResetPasswordMailConfirmed) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetResetPassword) - } - - override fun onError(throwable: Throwable) { - // Link in email not yet clicked ? - val message = if (throwable.is401()) { - getString(R.string.auth_reset_password_error_unauthorized) - } else { - errorFormatter.toHumanReadable(throwable) - } - - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .show() - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt deleted file mode 100644 index 33ebd13f2a..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import im.vector.app.databinding.FragmentLoginResetPasswordSuccess2Binding -import javax.inject.Inject - -/** - * In this screen, we confirm to the user that his password has been reset. - */ -class LoginResetPasswordSuccessFragment2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordSuccess2Binding { - return FragmentLoginResetPasswordSuccess2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - views.resetPasswordSuccessSubmit.setOnClickListener { submit() } - } - - private fun submit() { - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnResetPasswordMailConfirmationSuccessDone)) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetResetPassword) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt deleted file mode 100644 index b338b96c5d..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import im.vector.app.R -import im.vector.app.core.extensions.setTextWithColoredPart -import im.vector.app.core.utils.openUrlInChromeCustomTab -import im.vector.app.databinding.FragmentLoginServerSelection2Binding -import im.vector.app.features.login.EMS_LINK -import javax.inject.Inject - -/** - * In this screen, the user will choose between matrix.org, or other type of homeserver. - */ -class LoginServerSelectionFragment2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerSelection2Binding { - return FragmentLoginServerSelection2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - initViews() - } - - private fun initViews() { - views.loginServerChoiceMatrixOrg.setOnClickListener { selectMatrixOrg() } - views.loginServerChoiceOther.setOnClickListener { selectOther() } - - views.loginServerChoiceEmsLearnMore.setTextWithColoredPart( - fullTextRes = R.string.login_server_modular_learn_more_about_ems, - coloredTextRes = R.string.login_server_modular_learn_more, - underline = true - ) - views.loginServerChoiceEmsLearnMore.setOnClickListener { - openUrlInChromeCustomTab(requireActivity(), null, EMS_LINK) - } - } - - private fun updateUi(state: LoginViewState2) { - when (state.signMode) { - SignMode2.Unknown -> Unit - SignMode2.SignUp -> { - views.loginServerTitle.setText(R.string.login_please_choose_a_server) - } - SignMode2.SignIn -> { - views.loginServerTitle.setText(R.string.login_please_select_your_server) - } - } - } - - private fun selectMatrixOrg() { - views.loginServerChoiceMatrixOrg.isChecked = true - loginViewModel.handle(LoginAction2.ChooseDefaultHomeServer) - } - - private fun selectOther() { - views.loginServerChoiceOther.isChecked = true - loginViewModel.handle(LoginAction2.EnterServerUrl) - } - - override fun onResume() { - super.onResume() - views.loginServerChoiceMatrixOrg.isChecked = false - views.loginServerChoiceOther.isChecked = false - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetHomeServerUrl) - } - - override fun updateWithState(state: LoginViewState2) { - updateUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt deleted file mode 100644 index 50f8f38634..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.annotation.SuppressLint -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import android.widget.ArrayAdapter -import androidx.core.view.isInvisible -import androidx.lifecycle.lifecycleScope -import com.google.android.material.textfield.TextInputLayout -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.resources.BuildMeta -import im.vector.app.core.utils.ensureProtocol -import im.vector.app.databinding.FragmentLoginServerUrlForm2Binding -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import reactivecircus.flowbinding.android.widget.textChanges -import java.net.UnknownHostException -import javax.inject.Inject -import javax.net.ssl.HttpsURLConnection - -/** - * In this screen, the user is prompted to enter a homeserver url. - */ -class LoginServerUrlFormFragment2 @Inject constructor( - private val buildMeta: BuildMeta, -) : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerUrlForm2Binding { - return FragmentLoginServerUrlForm2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - setupHomeServerField() - } - - private fun setupViews() { - views.loginServerUrlFormClearHistory.setOnClickListener { clearHistory() } - views.loginServerUrlFormSubmit.setOnClickListener { submit() } - } - - private fun setupHomeServerField() { - views.loginServerUrlFormHomeServerUrl.textChanges() - .onEach { - views.loginServerUrlFormHomeServerUrlTil.error = null - views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank() - } - .launchIn(viewLifecycleOwner.lifecycleScope) - - views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - views.loginServerUrlFormHomeServerUrl.dismissDropDown() - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupUi(state: LoginViewState2) { - val completions = state.knownCustomHomeServersUrls + if (buildMeta.isDebug) listOf("http://10.0.2.2:8080") else emptyList() - views.loginServerUrlFormHomeServerUrl.setAdapter( - ArrayAdapter( - requireContext(), - R.layout.item_completion_homeserver, - completions - ) - ) - views.loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU - .takeIf { completions.isNotEmpty() } - ?: TextInputLayout.END_ICON_NONE - - views.loginServerUrlFormClearHistory.isInvisible = state.knownCustomHomeServersUrls.isEmpty() - } - - private fun clearHistory() { - loginViewModel.handle(LoginAction2.ClearHomeServerHistory) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetHomeServerUrl) - } - - @SuppressLint("SetTextI18n") - private fun submit() { - cleanupUi() - - // Static check of homeserver url, empty, malformed, etc. - val serverUrl = views.loginServerUrlFormHomeServerUrl.text.toString().trim().ensureProtocol() - - when { - serverUrl.isBlank() -> { - views.loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server) - } - else -> { - views.loginServerUrlFormHomeServerUrl.setText(serverUrl, false /* to avoid completion dialog flicker*/) - loginViewModel.handle(LoginAction2.UpdateHomeServer(serverUrl)) - } - } - } - - private fun cleanupUi() { - views.loginServerUrlFormSubmit.hideKeyboard() - views.loginServerUrlFormHomeServerUrlTil.error = null - } - - override fun onError(throwable: Throwable) { - views.loginServerUrlFormHomeServerUrlTil.error = if (throwable is Failure.NetworkConnection && - throwable.ioException is UnknownHostException) { - // Invalid homeserver? - getString(R.string.login_error_homeserver_not_found) - } else { - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) { - getString(R.string.login_registration_disabled) - } else { - errorFormatter.toHumanReadable(throwable) - } - } - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt deleted file mode 100644 index 84af28f75e..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.annotation.SuppressLint -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import im.vector.app.core.resources.BuildMeta -import im.vector.app.databinding.FragmentLoginSplash2Binding -import im.vector.app.features.settings.VectorPreferences -import javax.inject.Inject - -/** - * In this screen, the user is asked to sign up or to sign in to the homeserver. - * This is the new splash screen. - */ -class LoginSplashSignUpSignInSelectionFragment2 @Inject constructor( - private val vectorPreferences: VectorPreferences, - private val buildMeta: BuildMeta, -) : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSplash2Binding { - return FragmentLoginSplash2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - } - - private fun setupViews() { - views.loginSignupSigninSignUp.setOnClickListener { signUp() } - views.loginSignupSigninSignIn.setOnClickListener { signIn() } - - if (buildMeta.isDebug || vectorPreferences.developerMode()) { - views.loginSplashVersion.isVisible = true - @SuppressLint("SetTextI18n") - views.loginSplashVersion.text = "Version : ${buildMeta.versionName}\n" + - "Branch: ${buildMeta.gitBranchName}\n" + - "Build: ${buildMeta.buildNumber}" - views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) } - } - } - - private fun signUp() { - loginViewModel.handle(LoginAction2.UpdateSignMode(SignMode2.SignUp)) - } - - private fun signIn() { - loginViewModel.handle(LoginAction2.UpdateSignMode(SignMode2.SignIn)) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignMode) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt deleted file mode 100644 index 7aa2150c98..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.withState -import im.vector.app.R -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.databinding.FragmentLoginSsoOnly2Binding -import im.vector.app.features.login.SSORedirectRouterActivity -import javax.inject.Inject - -/** - * In this screen, the user is asked to sign up or to sign in to the homeserver. - */ -class LoginSsoOnlyFragment2 @Inject constructor() : AbstractSSOLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSsoOnly2Binding { - return FragmentLoginSsoOnly2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - } - - private fun setupViews() { - views.loginSignupSigninSubmit.setOnClickListener { submit() } - } - - private fun setupUi(state: LoginViewState2) { - views.loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrlFromUser.toReducedUrl()) - } - - private fun submit() = withState(loginViewModel) { state -> - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = null - ) - ?.let { openInCustomTab(it) } - } - - override fun resetViewModel() { - // No op - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt deleted file mode 100644 index 11a441923e..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package im.vector.app.features.login2 - -import im.vector.app.core.platform.VectorViewEvents -import org.matrix.android.sdk.api.auth.registration.FlowResult - -/** - * Transient events for Login. - */ -sealed class LoginViewEvents2 : VectorViewEvents { - data class Failure(val throwable: Throwable) : LoginViewEvents2() - - data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : LoginViewEvents2() - object OutdatedHomeserver : LoginViewEvents2() - - // Navigation event - object OpenSigninPasswordScreen : LoginViewEvents2() - object OpenSignupPasswordScreen : LoginViewEvents2() - - object OpenSignInEnterIdentifierScreen : LoginViewEvents2() - - object OpenSignUpChooseUsernameScreen : LoginViewEvents2() - object OpenSignInWithAnythingScreen : LoginViewEvents2() - - object OpenSsoOnlyScreen : LoginViewEvents2() - - object OpenServerSelection : LoginViewEvents2() - object OpenHomeServerUrlFormScreen : LoginViewEvents2() - - object OpenResetPasswordScreen : LoginViewEvents2() - object OnResetPasswordSendThreePidDone : LoginViewEvents2() - object OnResetPasswordMailConfirmationSuccess : LoginViewEvents2() - object OnResetPasswordMailConfirmationSuccessDone : LoginViewEvents2() - - object CancelRegistration : LoginViewEvents2() - - data class OnLoginModeNotSupported(val supportedTypes: List) : LoginViewEvents2() - - data class OnSendEmailSuccess(val email: String) : LoginViewEvents2() - data class OnSendMsisdnSuccess(val msisdn: String) : LoginViewEvents2() - - data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginViewEvents2() - - data class OnSessionCreated(val newAccount: Boolean) : LoginViewEvents2() - - object Finish : LoginViewEvents2() -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt deleted file mode 100644 index 834612c4f4..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ /dev/null @@ -1,829 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.content.Context -import android.net.Uri -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksViewModelFactory -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.di.MavericksAssistedViewModelFactory -import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.configureAndStart -import im.vector.app.core.extensions.tryAsync -import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.ensureTrailingSlash -import im.vector.app.features.login.HomeServerConnectionConfigFactory -import im.vector.app.features.login.LoginConfig -import im.vector.app.features.login.LoginMode -import im.vector.app.features.login.ReAuthHelper -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getServerName -import org.matrix.android.sdk.api.auth.AuthenticationService -import org.matrix.android.sdk.api.auth.HomeServerHistoryService -import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.auth.data.LoginFlowTypes -import org.matrix.android.sdk.api.auth.login.LoginWizard -import org.matrix.android.sdk.api.auth.registration.FlowResult -import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import org.matrix.android.sdk.api.auth.registration.RegistrationWizard -import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.auth.wellknown.WellknownResult -import org.matrix.android.sdk.api.session.Session -import timber.log.Timber -import java.util.concurrent.CancellationException - -/** - * - */ -class LoginViewModel2 @AssistedInject constructor( - @Assisted initialState: LoginViewState2, - private val applicationContext: Context, - private val authenticationService: AuthenticationService, - private val activeSessionHolder: ActiveSessionHolder, - private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory, - private val reAuthHelper: ReAuthHelper, - private val stringProvider: StringProvider, - private val homeServerHistoryService: HomeServerHistoryService -) : VectorViewModel(initialState) { - - @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: LoginViewState2): LoginViewModel2 - } - - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - - init { - getKnownCustomHomeServersUrls() - } - - private fun getKnownCustomHomeServersUrls() { - setState { - copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls()) - } - } - - // Store the last action, to redo it after user has trusted the untrusted certificate - private var lastAction: LoginAction2? = null - private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null - - private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() - - val currentThreePid: String? - get() = registrationWizard?.getCurrentThreePid() - - // True when login and password has been sent with success to the homeserver - val isRegistrationStarted: Boolean - get() = authenticationService.isRegistrationStarted() - - private val registrationWizard: RegistrationWizard? - get() = authenticationService.getRegistrationWizard() - - private val loginWizard: LoginWizard? - get() = authenticationService.getLoginWizard() - - private var loginConfig: LoginConfig? = null - - private var currentJob: Job? = null - set(value) { - // Cancel any previous Job - field?.cancel() - field = value - } - - override fun handle(action: LoginAction2) { - when (action) { - is LoginAction2.EnterServerUrl -> handleEnterServerUrl() - is LoginAction2.ChooseAServerForSignin -> handleChooseAServerForSignin() - is LoginAction2.UpdateSignMode -> handleUpdateSignMode(action) - is LoginAction2.InitWith -> handleInitWith(action) - is LoginAction2.ChooseDefaultHomeServer -> handle(LoginAction2.UpdateHomeServer(matrixOrgUrl)) - is LoginAction2.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action } - is LoginAction2.SetUserName -> handleSetUserName(action).also { lastAction = action } - is LoginAction2.SetUserPassword -> handleSetUserPassword(action).also { lastAction = action } - is LoginAction2.LoginWith -> handleLoginWith(action).also { lastAction = action } - is LoginAction2.LoginWithToken -> handleLoginWithToken(action) - is LoginAction2.WebLoginSuccess -> handleWebLoginSuccess(action) - is LoginAction2.ResetPassword -> handleResetPassword(action) - is LoginAction2.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed() - is LoginAction2.RegisterAction -> handleRegisterAction(action) - is LoginAction2.ResetAction -> handleResetAction(action) - is LoginAction2.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action) - is LoginAction2.UserAcceptCertificate -> handleUserAcceptCertificate(action) - LoginAction2.ClearHomeServerHistory -> handleClearHomeServerHistory() - is LoginAction2.PostViewEvent -> _viewEvents.post(action.viewEvent) - is LoginAction2.Finish -> handleFinish() - } - } - - private fun handleFinish() { - // Just post a view Event - _viewEvents.post(LoginViewEvents2.Finish) - } - - private fun handleChooseAServerForSignin() { - // Just post a view Event - _viewEvents.post(LoginViewEvents2.OpenServerSelection) - } - - private fun handleUserAcceptCertificate(action: LoginAction2.UserAcceptCertificate) { - // It happens when we get the login flow, or during direct authentication. - // So alter the homeserver config and retrieve again the login flow - when (val finalLastAction = lastAction) { - is LoginAction2.UpdateHomeServer -> { - currentHomeServerConnectionConfig - ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { getLoginFlow(it) } - } - is LoginAction2.SetUserName -> - handleSetUserNameForSignIn( - finalLastAction, - HomeServerConnectionConfig.Builder() - // Will be replaced by the task - .withHomeServerUri("https://dummy.org") - .withAllowedFingerPrints(listOf(action.fingerprint)) - .build() - ) - is LoginAction2.SetUserPassword -> - handleSetUserPassword(finalLastAction) - is LoginAction2.LoginWith -> - handleLoginWith(finalLastAction) - else -> Unit - } - } - - private fun rememberHomeServer(homeServerUrl: String) { - homeServerHistoryService.addHomeServerToHistory(homeServerUrl) - getKnownCustomHomeServersUrls() - } - - private fun handleClearHomeServerHistory() { - homeServerHistoryService.clearHistory() - getKnownCustomHomeServersUrls() - } - - private fun handleLoginWithToken(action: LoginAction2.LoginWithToken) { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - try { - safeLoginWizard.loginWithToken(action.loginToken) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - null - } - ?.let { onSessionCreated(it) } - - setState { copy(isLoading = false) } - } - } - } - - private fun handleSetupSsoForSessionRecovery(action: LoginAction2.SetupSsoForSessionRecovery) { - setState { - copy( - signMode = SignMode2.SignIn, - loginMode = LoginMode.Sso(action.ssoIdentityProviders), - homeServerUrlFromUser = action.homeServerUrl, - homeServerUrl = action.homeServerUrl, - deviceId = action.deviceId - ) - } - } - - private fun handleRegisterAction(action: LoginAction2.RegisterAction) { - when (action) { - is LoginAction2.CaptchaDone -> handleCaptchaDone(action) - is LoginAction2.AcceptTerms -> handleAcceptTerms() - is LoginAction2.RegisterDummy -> handleRegisterDummy() - is LoginAction2.AddThreePid -> handleAddThreePid(action) - is LoginAction2.SendAgainThreePid -> handleSendAgainThreePid() - is LoginAction2.ValidateThreePid -> handleValidateThreePid(action) - is LoginAction2.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action) - is LoginAction2.StopEmailValidationCheck -> handleStopEmailValidationCheck() - } - } - - private fun handleCheckIfEmailHasBeenValidated(action: LoginAction2.CheckIfEmailHasBeenValidated) { - // We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state - currentJob = executeRegistrationStep(withLoading = false) { - it.checkIfEmailHasBeenValidated(action.delayMillis) - } - } - - private fun handleStopEmailValidationCheck() { - currentJob = null - } - - private fun handleValidateThreePid(action: LoginAction2.ValidateThreePid) { - currentJob = executeRegistrationStep { - it.handleValidateThreePid(action.code) - } - } - - private fun executeRegistrationStep( - withLoading: Boolean = true, - block: suspend (RegistrationWizard) -> RegistrationResult - ): Job { - if (withLoading) { - setState { copy(isLoading = true) } - } - return viewModelScope.launch { - try { - registrationWizard?.let { block(it) } - } catch (failure: Throwable) { - if (failure !is CancellationException) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - } - null - } - ?.let { data -> - when (data) { - is RegistrationResult.Success -> onSessionCreated(data.session) - is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) - } - } - - setState { copy(isLoading = false) } - } - } - - private fun handleAddThreePid(action: LoginAction2.AddThreePid) { - setState { copy(isLoading = true) } - currentJob = viewModelScope.launch { - try { - registrationWizard?.addThreePid(action.threePid) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - } - setState { copy(isLoading = false) } - } - } - - private fun handleSendAgainThreePid() { - setState { copy(isLoading = true) } - currentJob = viewModelScope.launch { - try { - registrationWizard?.sendAgainThreePid() - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - } - setState { copy(isLoading = false) } - } - } - - private fun handleAcceptTerms() { - currentJob = executeRegistrationStep { - it.acceptTerms() - } - } - - private fun handleRegisterDummy() { - currentJob = executeRegistrationStep { - it.dummy() - } - } - - /** - * Check that the user name is available. - */ - private fun handleSetUserNameForSignUp(action: LoginAction2.SetUserName) { - setState { copy(isLoading = true) } - - val safeRegistrationWizard = registrationWizard ?: error("Invalid") - - viewModelScope.launch { - val available = safeRegistrationWizard.registrationAvailable(action.username) - - val event = when (available) { - RegistrationAvailability.Available -> { - // Ask for a password - LoginViewEvents2.OpenSignupPasswordScreen - } - is RegistrationAvailability.NotAvailable -> { - LoginViewEvents2.Failure(available.failure) - } - } - _viewEvents.post(event) - setState { copy(isLoading = false) } - } - } - - private fun handleCaptchaDone(action: LoginAction2.CaptchaDone) { - currentJob = executeRegistrationStep { - it.performReCaptcha(action.captchaResponse) - } - } - - // TODO Update this - private fun handleResetAction(action: LoginAction2.ResetAction) { - // Cancel any request - currentJob = null - - when (action) { - LoginAction2.ResetHomeServerUrl -> { - viewModelScope.launch { - authenticationService.reset() - setState { - copy( - homeServerUrlFromUser = null, - homeServerUrl = null, - loginMode = LoginMode.Unknown - ) - } - } - } - LoginAction2.ResetSignMode -> { - setState { - copy( - signMode = SignMode2.Unknown, - loginMode = LoginMode.Unknown - ) - } - } - LoginAction2.ResetSignin -> { - viewModelScope.launch { - authenticationService.cancelPendingLoginOrRegistration() - setState { - copy(isLoading = false) - } - } - _viewEvents.post(LoginViewEvents2.CancelRegistration) - } - LoginAction2.ResetSignup -> { - viewModelScope.launch { - authenticationService.cancelPendingLoginOrRegistration() - setState { - // Always create a new state, to ensure the state is correctly reset - LoginViewState2( - knownCustomHomeServersUrls = knownCustomHomeServersUrls - ) - } - } - _viewEvents.post(LoginViewEvents2.CancelRegistration) - } - LoginAction2.ResetResetPassword -> { - setState { - copy( - resetPasswordEmail = null, - resetPasswordNewPassword = null - ) - } - } - } - } - - private fun handleUpdateSignMode(action: LoginAction2.UpdateSignMode) { - setState { - copy( - signMode = action.signMode - ) - } - - when (action.signMode) { - SignMode2.SignUp -> _viewEvents.post(LoginViewEvents2.OpenServerSelection) - SignMode2.SignIn -> _viewEvents.post(LoginViewEvents2.OpenSignInEnterIdentifierScreen) - SignMode2.Unknown -> Unit - } - } - - private fun handleEnterServerUrl() { - _viewEvents.post(LoginViewEvents2.OpenHomeServerUrlFormScreen) - } - - private fun handleInitWith(action: LoginAction2.InitWith) { - loginConfig = action.loginConfig - - // If there is a pending email validation continue on this step - try { - if (registrationWizard?.isRegistrationStarted() == true) { - currentThreePid?.let { - handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnSendEmailSuccess(it))) - } - } - } catch (e: Throwable) { - // NOOP. API is designed to use wizards in a login/registration flow, - // but we need to check the state anyway. - } - } - - private fun handleResetPassword(action: LoginAction2.ResetPassword) { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - try { - safeLoginWizard.resetPassword(action.email) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - setState { copy(isLoading = false) } - return@launch - } - - setState { - copy( - isLoading = false, - resetPasswordEmail = action.email, - resetPasswordNewPassword = action.newPassword - ) - } - - _viewEvents.post(LoginViewEvents2.OnResetPasswordSendThreePidDone) - } - } - } - - private fun handleResetPasswordMailConfirmed() { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - try { - val state = awaitState() - safeLoginWizard.resetPasswordMailConfirmed(state.resetPasswordNewPassword!!) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - setState { copy(isLoading = false) } - return@launch - } - setState { - copy( - isLoading = false, - resetPasswordEmail = null, - resetPasswordNewPassword = null - ) - } - - _viewEvents.post(LoginViewEvents2.OnResetPasswordMailConfirmationSuccess) - } - } - } - - private fun handleSetUserName(action: LoginAction2.SetUserName) = withState { state -> - setState { - copy( - userName = action.username - ) - } - - when (state.signMode) { - SignMode2.Unknown -> error("Developer error, invalid sign mode") - SignMode2.SignIn -> handleSetUserNameForSignIn(action, null) - SignMode2.SignUp -> handleSetUserNameForSignUp(action) - } - } - - private fun handleSetUserPassword(action: LoginAction2.SetUserPassword) = withState { state -> - when (state.signMode) { - SignMode2.Unknown -> error("Developer error, invalid sign mode") - SignMode2.SignIn -> handleSignInWithPassword(action) - SignMode2.SignUp -> handleRegisterWithPassword(action) - } - } - - private fun handleRegisterWithPassword(action: LoginAction2.SetUserPassword) = withState { state -> - val username = state.userName ?: error("Developer error, username not set") - - reAuthHelper.data = action.password - currentJob = executeRegistrationStep { - it.createAccount( - userName = username, - password = action.password, - initialDeviceDisplayName = stringProvider.getString(R.string.login_default_session_public_name) - ) - } - } - - private fun handleSignInWithPassword(action: LoginAction2.SetUserPassword) = withState { state -> - val username = state.userName ?: error("Developer error, username not set") - setState { copy(isLoading = true) } - loginWith(username, action.password) - } - - private fun handleLoginWith(action: LoginAction2.LoginWith) { - setState { copy(isLoading = true) } - loginWith(action.login, action.password) - } - - private fun loginWith(login: String, password: String) { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Bad configuration"))) - setState { copy(isLoading = false) } - } else { - currentJob = viewModelScope.launch { - try { - safeLoginWizard.login( - login = login, - password = password, - initialDeviceName = stringProvider.getString(R.string.login_default_session_public_name) - ) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - null - } - ?.let { - reAuthHelper.data = password - onSessionCreated(it) - } - setState { copy(isLoading = false) } - } - } - } - - /** - * Perform wellknown request. - */ - private fun handleSetUserNameForSignIn(action: LoginAction2.SetUserName, homeServerConnectionConfig: HomeServerConnectionConfig?) { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - val data = try { - authenticationService.getWellKnownData(action.username, homeServerConnectionConfig) - } catch (failure: Throwable) { - onDirectLoginError(failure) - return@launch - } - when (data) { - is WellknownResult.Prompt -> - onWellknownSuccess(action, data, homeServerConnectionConfig) - is WellknownResult.FailPrompt -> - // Relax on IS discovery if homeserver is valid - if (data.homeServerUrl != null && data.wellKnown != null) { - onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig) - } else { - onWellKnownError() - } - else -> { - onWellKnownError() - } - } - } - } - - private fun onWellKnownError() { - _viewEvents.post(LoginViewEvents2.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) - setState { copy(isLoading = false) } - } - - private suspend fun onWellknownSuccess( - action: LoginAction2.SetUserName, - wellKnownPrompt: WellknownResult.Prompt, - homeServerConnectionConfig: HomeServerConnectionConfig? - ) { - val alteredHomeServerConnectionConfig = homeServerConnectionConfig - ?.copy( - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - ?: HomeServerConnectionConfig( - homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - - // Ensure login flow is retrieved, and this is not a SSO only server - val data = try { - authenticationService.getLoginFlow(alteredHomeServerConnectionConfig) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - null - } ?: return - - val loginMode = when { - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } - - val viewEvent = when (loginMode) { - LoginMode.Password, - is LoginMode.SsoAndPassword -> { - retrieveProfileInfo(action.username) - // We can navigate to the password screen - LoginViewEvents2.OpenSigninPasswordScreen - } - is LoginMode.Sso -> { - LoginViewEvents2.OpenSsoOnlyScreen - } - LoginMode.Unsupported -> LoginViewEvents2.OnLoginModeNotSupported(data.supportedLoginTypes.toList()) - LoginMode.Unknown -> null - } - viewEvent?.let { _viewEvents.post(it) } - - val urlFromUser = action.username.getServerName() - setState { - copy( - isLoading = false, - homeServerUrlFromUser = urlFromUser, - homeServerUrl = data.homeServerUrl, - loginMode = loginMode - ) - } - - if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) || - data.isOutdatedHomeserver) { - // Notify the UI - _viewEvents.post(LoginViewEvents2.OutdatedHomeserver) - } - } - - private suspend fun retrieveProfileInfo(username: String) { - val safeLoginWizard = loginWizard - - if (safeLoginWizard != null) { - setState { copy(loginProfileInfo = Loading()) } - val result = tryAsync { - safeLoginWizard.getProfileInfo(username) - } - setState { copy(loginProfileInfo = result) } - } - } - - private fun onDirectLoginError(failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - setState { copy(isLoading = false) } - } - - private fun onFlowResponse(flowResult: FlowResult) { - // If dummy stage is mandatory, and password is already sent, do the dummy stage now - if (isRegistrationStarted && - flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) { - handleRegisterDummy() - } else { - // Notify the user - _viewEvents.post(LoginViewEvents2.RegistrationFlowResult(flowResult, isRegistrationStarted)) - } - } - - private suspend fun onSessionCreated(session: Session) { - activeSessionHolder.setActiveSession(session) - - authenticationService.reset() - session.configureAndStart(applicationContext) - withState { state -> - _viewEvents.post(LoginViewEvents2.OnSessionCreated(state.signMode == SignMode2.SignUp)) - } - } - - private fun handleWebLoginSuccess(action: LoginAction2.WebLoginSuccess) = withState { state -> - val homeServerConnectionConfigFinal = homeServerConnectionConfigFactory.create(state.homeServerUrl) - - if (homeServerConnectionConfigFinal == null) { - // Should not happen - Timber.w("homeServerConnectionConfig is null") - } else { - currentJob = viewModelScope.launch { - try { - authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - null - } - ?.let { onSessionCreated(it) } - } - } - } - - private fun handleUpdateHomeserver(action: LoginAction2.UpdateHomeServer) { - val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl) - if (homeServerConnectionConfig == null) { - // This is invalid - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) - } else { - getLoginFlow(homeServerConnectionConfig) - } - } - - private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) = withState { state -> - currentHomeServerConnectionConfig = homeServerConnectionConfig - - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - authenticationService.cancelPendingLoginOrRegistration() - - val data = try { - authenticationService.getLoginFlow(homeServerConnectionConfig) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - setState { copy(isLoading = false) } - null - } ?: return@launch - - // Valid Homeserver, add it to the history. - // Note: we add what the user has input, data.homeServerUrlBase can be different - rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString()) - - val loginMode = when { - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } - - val viewEvent = when (loginMode) { - LoginMode.Password, - is LoginMode.SsoAndPassword -> { - when (state.signMode) { - SignMode2.Unknown -> null - SignMode2.SignUp -> { - // Check that registration is possible on this server - try { - registrationWizard?.getRegistrationFlow() - - /* - // Simulate registration disabled - throw Failure.ServerError( - error = MatrixError( - code = MatrixError.M_FORBIDDEN, - message = "Registration is disabled" - ), - httpCode = 403 - ) - */ - - LoginViewEvents2.OpenSignUpChooseUsernameScreen - } catch (throwable: Throwable) { - // Registration disabled? - LoginViewEvents2.Failure(throwable) - } - } - SignMode2.SignIn -> LoginViewEvents2.OpenSignInWithAnythingScreen - } - } - is LoginMode.Sso -> { - LoginViewEvents2.OpenSsoOnlyScreen - } - LoginMode.Unsupported -> LoginViewEvents2.OnLoginModeNotSupported(data.supportedLoginTypes.toList()) - LoginMode.Unknown -> null - } - viewEvent?.let { _viewEvents.post(it) } - - if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) || - data.isOutdatedHomeserver) { - // Notify the UI - _viewEvents.post(LoginViewEvents2.OutdatedHomeserver) - } - - setState { - copy( - isLoading = false, - homeServerUrlFromUser = homeServerConnectionConfig.homeServerUri.toString(), - homeServerUrl = data.homeServerUrl, - loginMode = loginMode - ) - } - } - } - - fun getInitialHomeServerUrl(): String? { - return loginConfig?.homeServerUrl - } - - fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { - return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId) - } - - fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? { - return authenticationService.getFallbackUrl(forSignIn, deviceId) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt deleted file mode 100644 index 8405381c4f..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.PersistState -import com.airbnb.mvrx.Uninitialized -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.features.login.LoginMode -import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.auth.login.LoginProfileInfo - -data class LoginViewState2( - val isLoading: Boolean = false, - - // User choices - @PersistState - val signMode: SignMode2 = SignMode2.Unknown, - @PersistState - val userName: String? = null, - @PersistState - val resetPasswordEmail: String? = null, - @PersistState - val resetPasswordNewPassword: String? = null, - @PersistState - val homeServerUrlFromUser: String? = null, - - // Can be modified after a Wellknown request - @PersistState - val homeServerUrl: String? = null, - - // For SSO session recovery - @PersistState - val deviceId: String? = null, - - // Network result - val loginProfileInfo: Async = Uninitialized, - - // Network result - @PersistState - val loginMode: LoginMode = LoginMode.Unknown, - - // From database - val knownCustomHomeServersUrls: List = emptyList() -) : MavericksState { - - // Pending user identifier - fun userIdentifier(): String { - return if (userName != null && MatrixPatterns.isUserId(userName)) { - userName - } else { - "@$userName:${homeServerUrlFromUser.toReducedUrl()}" - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt deleted file mode 100644 index 772db7be5f..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.args -import im.vector.app.R -import im.vector.app.databinding.FragmentLoginWaitForEmail2Binding -import im.vector.app.features.login.LoginWaitForEmailFragmentArgument -import org.matrix.android.sdk.api.failure.is401 -import javax.inject.Inject - -/** - * In this screen, the user is asked to check their emails. - */ -class LoginWaitForEmailFragment2 @Inject constructor() : AbstractLoginFragment2() { - - private val params: LoginWaitForEmailFragmentArgument by args() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWaitForEmail2Binding { - return FragmentLoginWaitForEmail2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupUi() - } - - override fun onResume() { - super.onResume() - - loginViewModel.handle(LoginAction2.CheckIfEmailHasBeenValidated(0)) - } - - override fun onPause() { - super.onPause() - - loginViewModel.handle(LoginAction2.StopEmailValidationCheck) - } - - private fun setupUi() { - views.loginWaitForEmailNotice.text = getString(R.string.login_wait_for_email_notice_2, params.email) - } - - override fun onError(throwable: Throwable) { - if (throwable.is401()) { - // Try again, with a delay - loginViewModel.handle(LoginAction2.CheckIfEmailHasBeenValidated(10_000)) - } else { - super.onError(throwable) - } - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignup) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt deleted file mode 100644 index 708efd9d56..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("DEPRECATION") - -package im.vector.app.features.login2 - -import android.annotation.SuppressLint -import android.content.DialogInterface -import android.graphics.Bitmap -import android.net.http.SslError -import android.os.Bundle -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.webkit.SslErrorHandler -import android.webkit.WebView -import android.webkit.WebViewClient -import com.airbnb.mvrx.activityViewModel -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.utils.AssetReader -import im.vector.app.databinding.FragmentLoginWebBinding -import im.vector.app.features.login.JavascriptResponse -import im.vector.app.features.signout.soft.SoftLogoutAction -import im.vector.app.features.signout.soft.SoftLogoutViewModel -import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.util.MatrixJsonParser -import timber.log.Timber -import java.net.URLDecoder -import javax.inject.Inject - -/** - * This screen is displayed when the application does not support login flow or registration flow - * of the homeserver, as a fallback to login or to create an account. - */ -class LoginWebFragment2 @Inject constructor( - private val assetReader: AssetReader -) : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding { - return FragmentLoginWebBinding.inflate(inflater, container, false) - } - - private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel() - - private var isWebViewLoaded = false - private var isForSessionRecovery = false - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupToolbar(views.loginWebToolbar) - .allowBack() - } - - override fun updateWithState(state: LoginViewState2) { - setupTitle(state) - - isForSessionRecovery = state.deviceId?.isNotBlank() == true - - if (!isWebViewLoaded) { - setupWebView(state) - isWebViewLoaded = true - } - } - - private fun setupTitle(state: LoginViewState2) { - toolbar?.title = when (state.signMode) { - SignMode2.SignIn -> getString(R.string.login_signin) - else -> getString(R.string.login_signup) - } - } - - @SuppressLint("SetJavaScriptEnabled") - private fun setupWebView(state: LoginViewState2) { - views.loginWebWebView.settings.javaScriptEnabled = true - - // Enable local storage to support SSO with Firefox accounts - views.loginWebWebView.settings.domStorageEnabled = true - views.loginWebWebView.settings.databaseEnabled = true - - // Due to https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html, we hack - // the user agent to bypass the limitation of Google, as a quick fix (a proper solution will be to use the SSO SDK) - views.loginWebWebView.settings.userAgentString = "Mozilla/5.0 Google" - - // AppRTC requires third party cookies to work - val cookieManager = android.webkit.CookieManager.getInstance() - - // clear the cookies - if (cookieManager == null) { - launchWebView(state) - } else { - if (!cookieManager.hasCookies()) { - launchWebView(state) - } else { - try { - cookieManager.removeAllCookies { launchWebView(state) } - } catch (e: Exception) { - Timber.e(e, " cookieManager.removeAllCookie() fails") - launchWebView(state) - } - } - } - } - - private fun launchWebView(state: LoginViewState2) { - val url = loginViewModel.getFallbackUrl(state.signMode == SignMode2.SignIn, state.deviceId) ?: return - - views.loginWebWebView.loadUrl(url) - - views.loginWebWebView.webViewClient = object : WebViewClient() { - override fun onReceivedSslError( - view: WebView, - handler: SslErrorHandler, - error: SslError - ) { - MaterialAlertDialogBuilder(requireActivity()) - .setMessage(R.string.ssl_could_not_verify) - .setPositiveButton(R.string.ssl_trust) { _, _ -> handler.proceed() } - .setNegativeButton(R.string.ssl_do_not_trust) { _, _ -> handler.cancel() } - .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - handler.cancel() - dialog.dismiss() - return@OnKeyListener true - } - false - }) - .setCancelable(false) - .show() - } - - @Deprecated("Deprecated in Java") - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { - super.onReceivedError(view, errorCode, description, failingUrl) - - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnWebLoginError(errorCode, description, failingUrl))) - } - - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - - toolbar?.subtitle = url - } - - override fun onPageFinished(view: WebView, url: String) { - // avoid infinite onPageFinished call - if (url.startsWith("http")) { - // Generic method to make a bridge between JS and the UIWebView - assetReader.readAssetFile("sendObject.js")?.let { view.loadUrl(it) } - - if (state.signMode == SignMode2.SignIn) { - // The function the fallback page calls when the login is complete - assetReader.readAssetFile("onLogin.js")?.let { view.loadUrl(it) } - } else { - // MODE_REGISTER - // The function the fallback page calls when the registration is complete - assetReader.readAssetFile("onRegistered.js")?.let { view.loadUrl(it) } - } - } - } - - /** - * Example of (formatted) url for MODE_LOGIN: - * - *
-             * js:{
-             *     "action":"onLogin",
-             *     "credentials":{
-             *         "user_id":"@user:matrix.org",
-             *         "access_token":"[ACCESS_TOKEN]",
-             *         "home_server":"matrix.org",
-             *         "device_id":"[DEVICE_ID]",
-             *         "well_known":{
-             *             "m.homeserver":{
-             *                 "base_url":"https://matrix.org/"
-             *                 }
-             *             }
-             *         }
-             *    }
-             *    .
-             * 
- * @param view - * @param url - * @return - */ - @Deprecated("Deprecated in Java") - override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { - if (url == null) return super.shouldOverrideUrlLoading(view, url as String?) - - if (url.startsWith("js:")) { - var json = url.substring(3) - var javascriptResponse: JavascriptResponse? = null - - try { - // URL decode - json = URLDecoder.decode(json, "UTF-8") - val adapter = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java) - javascriptResponse = adapter.fromJson(json) - } catch (e: Exception) { - Timber.e(e, "## shouldOverrideUrlLoading() : fromJson failed") - } - - // succeeds to parse parameters - if (javascriptResponse != null) { - val action = javascriptResponse.action - - if (state.signMode == SignMode2.SignIn) { - if (action == "onLogin") { - javascriptResponse.credentials?.let { notifyViewModel(it) } - } - } else { - // MODE_REGISTER - // check the required parameters - if (action == "onRegistered") { - javascriptResponse.credentials?.let { notifyViewModel(it) } - } - } - } - return true - } - - return super.shouldOverrideUrlLoading(view, url) - } - } - } - - private fun notifyViewModel(credentials: Credentials) { - if (isForSessionRecovery) { - softLogoutViewModel.handle(SoftLogoutAction.WebLoginSuccess(credentials)) - } else { - loginViewModel.handle(LoginAction2.WebLoginSuccess(credentials)) - } - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignin) - } - - override fun onBackPressed(toolbarButton: Boolean): Boolean { - return when { - toolbarButton -> super.onBackPressed(toolbarButton) - views.loginWebWebView.canGoBack() -> views.loginWebWebView.goBack().run { true } - else -> super.onBackPressed(toolbarButton) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/SignMode2.kt b/vector/src/main/java/im/vector/app/features/login2/SignMode2.kt deleted file mode 100644 index f3d59837e7..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/SignMode2.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -enum class SignMode2 { - Unknown, - - // Account creation - SignUp, - - // Login - SignIn -} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt deleted file mode 100644 index f108bfa886..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2.created - -import android.net.Uri -import im.vector.app.core.platform.VectorViewModelAction - -sealed class AccountCreatedAction : VectorViewModelAction { - data class SetDisplayName(val displayName: String) : AccountCreatedAction() - data class SetAvatar(val avatarUri: Uri, val filename: String) : AccountCreatedAction() -} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt deleted file mode 100644 index d549c22028..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2.created - -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.date.DateFormatKind -import im.vector.app.core.date.VectorDateFormatter -import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper -import im.vector.app.core.intent.getFilenameFromUri -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock -import im.vector.app.databinding.DialogBaseEditTextBinding -import im.vector.app.databinding.FragmentLoginAccountCreatedBinding -import im.vector.app.features.displayname.getBestName -import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider -import im.vector.app.features.login2.AbstractLoginFragment2 -import im.vector.app.features.login2.LoginAction2 -import im.vector.app.features.login2.LoginViewState2 -import im.vector.app.features.onboarding.OnboardingActivity -import org.matrix.android.sdk.api.util.MatrixItem -import java.util.UUID -import javax.inject.Inject - -/** - * In this screen: - * - the account has been created and we propose the user to set an avatar and a display name. - */ -class AccountCreatedFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer, - private val dateFormatter: VectorDateFormatter, - private val matrixItemColorProvider: MatrixItemColorProvider, - private val clock: Clock, - colorProvider: ColorProvider -) : AbstractLoginFragment2(), - GalleryOrCameraDialogHelper.Listener { - - private val viewModel: AccountCreatedViewModel by fragmentViewModel() - - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginAccountCreatedBinding { - return FragmentLoginAccountCreatedBinding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupClickListener() - setupSubmitButton() - observeViewEvents() - - viewModel.onEach { invalidateState(it) } - - views.loginAccountCreatedTime.text = dateFormatter.format(clock.epochMillis(), DateFormatKind.MESSAGE_SIMPLE) - } - - private fun observeViewEvents() { - viewModel.observeViewEvents { - when (it) { - is AccountCreatedViewEvents.Failure -> displayErrorDialog(it.throwable) - } - } - } - - private fun setupClickListener() { - views.loginAccountCreatedMessage.debouncedClicks { - // Update display name - displayDialog() - } - views.loginAccountCreatedAvatar.debouncedClicks { - galleryOrCameraDialogHelper.show() - } - } - - private fun displayDialog() = withState(viewModel) { state -> - val inflater = requireActivity().layoutInflater - val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) - val views = DialogBaseEditTextBinding.bind(layout) - views.editText.setText(state.currentUser()?.getBestName().orEmpty()) - - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.settings_display_name) - .setView(layout) - .setPositiveButton(R.string.ok) { _, _ -> - val newName = views.editText.text.toString() - viewModel.handle(AccountCreatedAction.SetDisplayName(newName)) - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } - - override fun onImageReady(uri: Uri?) { - uri ?: return - viewModel.handle( - AccountCreatedAction.SetAvatar( - avatarUri = uri, - filename = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString() - ) - ) - } - - private fun setupSubmitButton() { - views.loginAccountCreatedLater.debouncedClicks { terminate() } - views.loginAccountCreatedDone.debouncedClicks { terminate() } - } - - private fun terminate() { - loginViewModel.handle(LoginAction2.Finish) - } - - private fun invalidateState(state: AccountCreatedViewState) { - // Ugly hack... - (activity as? OnboardingActivity)?.setIsLoading(state.isLoading) - - views.loginAccountCreatedSubtitle.text = getString(R.string.login_account_created_subtitle, state.userId) - - val user = state.currentUser() - if (user != null) { - avatarRenderer.render(user, views.loginAccountCreatedAvatar) - views.loginAccountCreatedMemberName.text = user.getBestName() - } else { - // Should not happen - views.loginAccountCreatedMemberName.text = state.userId - } - - // User color - views.loginAccountCreatedMemberName - .setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(state.userId))) - - views.loginAccountCreatedLater.isVisible = state.hasBeenModified.not() - views.loginAccountCreatedDone.isVisible = state.hasBeenModified - } - - override fun updateWithState(state: LoginViewState2) { - // No op - } - - override fun resetViewModel() { - // No op - } - - override fun onBackPressed(toolbarButton: Boolean): Boolean { - // Just start the next Activity - terminate() - return false - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt deleted file mode 100644 index 6a23409f5c..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package im.vector.app.features.login2.created - -import im.vector.app.core.platform.VectorViewEvents - -/** - * Transient events for Account Created. - */ -sealed class AccountCreatedViewEvents : VectorViewEvents { - data class Failure(val throwable: Throwable) : AccountCreatedViewEvents() -} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt deleted file mode 100644 index c3b7f2d3d3..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2.created - -import com.airbnb.mvrx.MavericksViewModelFactory -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import im.vector.app.core.di.MavericksAssistedViewModelFactory -import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.MatrixItem -import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap -import timber.log.Timber - -class AccountCreatedViewModel @AssistedInject constructor( - @Assisted initialState: AccountCreatedViewState, - private val session: Session -) : VectorViewModel(initialState) { - - @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: AccountCreatedViewState): AccountCreatedViewModel - } - - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - - init { - setState { - copy( - userId = session.myUserId - ) - } - observeUser() - } - - private fun observeUser() { - session.flow() - .liveUser(session.myUserId) - .unwrap() - .map { - if (MatrixPatterns.isUserId(it.userId)) { - it.toMatrixItem() - } else { - Timber.w("liveUser() has returned an invalid user: $it") - MatrixItem.UserItem(session.myUserId, null, null) - } - } - .execute { - copy(currentUser = it) - } - } - - override fun handle(action: AccountCreatedAction) { - when (action) { - is AccountCreatedAction.SetAvatar -> handleSetAvatar(action) - is AccountCreatedAction.SetDisplayName -> handleSetDisplayName(action) - } - } - - private fun handleSetAvatar(action: AccountCreatedAction.SetAvatar) { - setState { copy(isLoading = true) } - viewModelScope.launch { - val result = runCatching { session.profileService().updateAvatar(session.myUserId, action.avatarUri, action.filename) } - .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } - setState { - copy( - isLoading = false, - hasBeenModified = hasBeenModified || result.isSuccess - ) - } - } - } - - private fun handleSetDisplayName(action: AccountCreatedAction.SetDisplayName) { - setState { copy(isLoading = true) } - viewModelScope.launch { - val result = runCatching { session.profileService().setDisplayName(session.myUserId, action.displayName) } - .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } - setState { - copy( - isLoading = false, - hasBeenModified = hasBeenModified || result.isSuccess - ) - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt deleted file mode 100644 index 0ae60e910c..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2.created - -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.Uninitialized -import org.matrix.android.sdk.api.util.MatrixItem - -data class AccountCreatedViewState( - val userId: String = "", - val isLoading: Boolean = false, - val currentUser: Async = Uninitialized, - val hasBeenModified: Boolean = false -) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt deleted file mode 100755 index a48996a16f..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2018 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2.terms - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.args -import im.vector.app.core.extensions.cleanup -import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.core.utils.openUrlInChromeCustomTab -import im.vector.app.databinding.FragmentLoginTerms2Binding -import im.vector.app.features.login.terms.LocalizedFlowDataLoginTermsChecked -import im.vector.app.features.login.terms.LoginTermsFragmentArgument -import im.vector.app.features.login.terms.LoginTermsViewState -import im.vector.app.features.login.terms.PolicyController -import im.vector.app.features.login2.AbstractLoginFragment2 -import im.vector.app.features.login2.LoginAction2 -import im.vector.app.features.login2.LoginViewState2 -import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms -import javax.inject.Inject - -/** - * LoginTermsFragment displays the list of policies the user has to accept. - */ -class LoginTermsFragment2 @Inject constructor( - private val policyController: PolicyController -) : AbstractLoginFragment2(), - PolicyController.PolicyControllerListener { - - private val params: LoginTermsFragmentArgument by args() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginTerms2Binding { - return FragmentLoginTerms2Binding.inflate(inflater, container, false) - } - - private var loginTermsViewState: LoginTermsViewState = LoginTermsViewState(emptyList()) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - views.loginTermsPolicyList.configureWith(policyController) - policyController.listener = this - - val list = ArrayList() - - params.localizedFlowDataLoginTerms - .forEach { - list.add(LocalizedFlowDataLoginTermsChecked(it)) - } - - loginTermsViewState = LoginTermsViewState(list) - } - - private fun setupViews() { - views.loginTermsSubmit.setOnClickListener { submit() } - } - - override fun onDestroyView() { - views.loginTermsPolicyList.cleanup() - policyController.listener = null - super.onDestroyView() - } - - private fun renderState() { - policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked) - - // Button is enabled only if all checkboxes are checked - views.loginTermsSubmit.isEnabled = loginTermsViewState.allChecked() - } - - override fun setChecked(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms, isChecked: Boolean) { - if (isChecked) { - loginTermsViewState.check(localizedFlowDataLoginTerms) - } else { - loginTermsViewState.uncheck(localizedFlowDataLoginTerms) - } - - renderState() - } - - override fun openPolicy(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms) { - localizedFlowDataLoginTerms.localizedUrl - ?.takeIf { it.isNotBlank() } - ?.let { - openUrlInChromeCustomTab(requireContext(), null, it) - } - } - - private fun submit() { - loginViewModel.handle(LoginAction2.AcceptTerms) - } - - override fun updateWithState(state: LoginViewState2) { - policyController.homeServer = state.homeServerUrlFromUser.toReducedUrl() - renderState() - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignup) - } -} diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index e724084501..4eec5c75a1 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -130,7 +130,6 @@ class DefaultNavigator @Inject constructor( override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { val intent = when (features.onboardingVariant()) { OnboardingVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig) - OnboardingVariant.LOGIN_2, OnboardingVariant.FTUE_AUTH -> OnboardingActivity.newIntent(context, loginConfig) } intent.addFlags(flags) @@ -140,7 +139,6 @@ class DefaultNavigator @Inject constructor( override fun loginSSORedirect(context: Context, data: Uri?) { val intent = when (features.onboardingVariant()) { OnboardingVariant.LEGACY -> LoginActivity.redirectIntent(context, data) - OnboardingVariant.LOGIN_2, OnboardingVariant.FTUE_AUTH -> OnboardingActivity.redirectIntent(context, data) } context.startActivity(intent) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt deleted file mode 100644 index 7def6d62f0..0000000000 --- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.onboarding - -import android.content.Intent -import android.view.View -import android.view.ViewGroup -import androidx.core.view.ViewCompat -import androidx.core.view.children -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentTransaction -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE -import im.vector.app.core.extensions.addFragment -import im.vector.app.core.extensions.addFragmentToBackstack -import im.vector.app.core.extensions.resetBackstack -import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.databinding.ActivityLoginBinding -import im.vector.app.features.home.HomeActivity -import im.vector.app.features.login.LoginCaptchaFragmentArgument -import im.vector.app.features.login.LoginConfig -import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument -import im.vector.app.features.login.LoginWaitForEmailFragmentArgument -import im.vector.app.features.login.TextInputFormFragmentMode -import im.vector.app.features.login.isSupported -import im.vector.app.features.login.terms.LoginTermsFragmentArgument -import im.vector.app.features.login2.LoginAction2 -import im.vector.app.features.login2.LoginCaptchaFragment2 -import im.vector.app.features.login2.LoginFragmentSigninPassword2 -import im.vector.app.features.login2.LoginFragmentSigninUsername2 -import im.vector.app.features.login2.LoginFragmentSignupPassword2 -import im.vector.app.features.login2.LoginFragmentSignupUsername2 -import im.vector.app.features.login2.LoginFragmentToAny2 -import im.vector.app.features.login2.LoginGenericTextInputFormFragment2 -import im.vector.app.features.login2.LoginResetPasswordFragment2 -import im.vector.app.features.login2.LoginResetPasswordMailConfirmationFragment2 -import im.vector.app.features.login2.LoginResetPasswordSuccessFragment2 -import im.vector.app.features.login2.LoginServerSelectionFragment2 -import im.vector.app.features.login2.LoginServerUrlFormFragment2 -import im.vector.app.features.login2.LoginSplashSignUpSignInSelectionFragment2 -import im.vector.app.features.login2.LoginSsoOnlyFragment2 -import im.vector.app.features.login2.LoginViewEvents2 -import im.vector.app.features.login2.LoginViewModel2 -import im.vector.app.features.login2.LoginViewState2 -import im.vector.app.features.login2.LoginWaitForEmailFragment2 -import im.vector.app.features.login2.LoginWebFragment2 -import im.vector.app.features.login2.created.AccountCreatedFragment -import im.vector.app.features.login2.terms.LoginTermsFragment2 -import org.matrix.android.sdk.api.auth.registration.FlowResult -import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms -import org.matrix.android.sdk.api.extensions.tryOrNull - -private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" -private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" - -class Login2Variant( - private val views: ActivityLoginBinding, - private val loginViewModel: LoginViewModel2, - private val activity: VectorBaseActivity, - private val supportFragmentManager: FragmentManager -) : OnboardingVariant { - - private val enterAnim = R.anim.enter_fade_in - private val exitAnim = R.anim.exit_fade_out - - private val popEnterAnim = R.anim.no_anim - private val popExitAnim = R.anim.exit_fade_out - - private val topFragment: Fragment? - get() = supportFragmentManager.findFragmentById(views.loginFragmentContainer.id) - - private val commonOption: (FragmentTransaction) -> Unit = { ft -> - // Find the loginLogo on the current Fragment, this should not return null - (topFragment?.view as? ViewGroup) - // Find activity.findViewById does not work, I do not know why - // activity.findViewById(views.loginLogo) - ?.children - ?.firstOrNull { it.id == R.id.loginLogo } - ?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) - } - - override fun initUiAndData(isFirstCreation: Boolean) { - if (isFirstCreation) { - addFirstFragment() - } - - with(activity) { - loginViewModel.onEach { - updateWithState(it) - } - loginViewModel.observeViewEvents { handleLoginViewEvents(it) } - } - - // Get config extra - val loginConfig = activity.intent.getParcelableExtra(OnboardingActivity.EXTRA_CONFIG) - if (isFirstCreation) { - // TODO Check this - loginViewModel.handle(LoginAction2.InitWith(loginConfig)) - } - } - - private fun addFirstFragment() { - activity.addFragment(views.loginFragmentContainer, LoginSplashSignUpSignInSelectionFragment2::class.java) - } - - private fun handleLoginViewEvents(event: LoginViewEvents2) { - when (event) { - is LoginViewEvents2.RegistrationFlowResult -> { - // Check that all flows are supported by the application - if (event.flowResult.missingStages.any { !it.isSupported() }) { - // Display a popup to propose use web fallback - onRegistrationStageNotSupported() - } else { - if (event.isRegistrationStarted) { - // Go on with registration flow - handleRegistrationNavigation(event.flowResult) - } else { - /* - // First ask for login and password - // I add a tag to indicate that this fragment is a registration stage. - // This way it will be automatically popped in when starting the next registration stage - activity.addFragmentToBackstack(views.loginFragmentContainer, - LoginFragment2::class.java, - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - - */ - } - } - } - is LoginViewEvents2.OutdatedHomeserver -> { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.login_error_outdated_homeserver_title) - .setMessage(R.string.login_error_outdated_homeserver_warning_content) - .setPositiveButton(R.string.ok, null) - .show() - Unit - } - is LoginViewEvents2.OpenServerSelection -> - activity.addFragmentToBackstack(views.loginFragmentContainer, - LoginServerSelectionFragment2::class.java, - option = { ft -> - activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // Disable transition of text - // activity.findViewById(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // No transition here now actually - // activity.findViewById(views.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // TODO Disabled because it provokes a flickering - // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) - }) - is LoginViewEvents2.OpenHomeServerUrlFormScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginServerUrlFormFragment2::class.java, - option = commonOption - ) - } - is LoginViewEvents2.OpenSignInEnterIdentifierScreen -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, - LoginFragmentSigninUsername2::class.java, - option = { ft -> - activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // Disable transition of text - // activity.findViewById(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // No transition here now actually - // activity.findViewById(views.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // TODO Disabled because it provokes a flickering - // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) - }) - } - is LoginViewEvents2.OpenSsoOnlyScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginSsoOnlyFragment2::class.java, - option = commonOption - ) - } - is LoginViewEvents2.OnWebLoginError -> onWebLoginError(event) - is LoginViewEvents2.OpenResetPasswordScreen -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginResetPasswordFragment2::class.java, - option = commonOption - ) - is LoginViewEvents2.OnResetPasswordSendThreePidDone -> { - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginResetPasswordMailConfirmationFragment2::class.java, - option = commonOption - ) - } - is LoginViewEvents2.OnResetPasswordMailConfirmationSuccess -> { - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginResetPasswordSuccessFragment2::class.java, - option = commonOption - ) - } - is LoginViewEvents2.OnResetPasswordMailConfirmationSuccessDone -> { - // Go back to the login fragment - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - } - is LoginViewEvents2.OnSendEmailSuccess -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginWaitForEmailFragment2::class.java, - LoginWaitForEmailFragmentArgument(event.email), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is LoginViewEvents2.OpenSigninPasswordScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginFragmentSigninPassword2::class.java, - tag = FRAGMENT_LOGIN_TAG, - option = commonOption - ) - } - is LoginViewEvents2.OpenSignupPasswordScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginFragmentSignupPassword2::class.java, - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - } - is LoginViewEvents2.OpenSignUpChooseUsernameScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginFragmentSignupUsername2::class.java, - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - } - is LoginViewEvents2.OpenSignInWithAnythingScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginFragmentToAny2::class.java, - tag = FRAGMENT_LOGIN_TAG, - option = commonOption - ) - } - is LoginViewEvents2.OnSendMsisdnSuccess -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginGenericTextInputFormFragment2::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, event.msisdn), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is LoginViewEvents2.Failure -> - // This is handled by the Fragments - Unit - is LoginViewEvents2.OnLoginModeNotSupported -> - onLoginModeNotSupported(event.supportedTypes) - is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event) - is LoginViewEvents2.Finish -> terminate() - is LoginViewEvents2.CancelRegistration -> handleCancelRegistration() - } - } - - private fun handleCancelRegistration() { - // Cleanup the back stack - activity.resetBackstack() - } - - private fun handleOnSessionCreated(event: LoginViewEvents2.OnSessionCreated) { - if (event.newAccount) { - // Propose to set avatar and display name - // Back on this Fragment will finish the Activity - activity.addFragmentToBackstack( - views.loginFragmentContainer, - AccountCreatedFragment::class.java, - option = commonOption - ) - } else { - terminate() - } - } - - private fun terminate() { - val intent = HomeActivity.newIntent( - activity, - firstStartMainActivity = false, - ) - activity.startActivity(intent) - activity.finish() - } - - private fun updateWithState(loginViewState2: LoginViewState2) { - // Loading - setIsLoading(loginViewState2.isLoading) - } - - // Hack for AccountCreatedFragment - override fun setIsLoading(isLoading: Boolean) { - views.loginLoading.isVisible = isLoading - } - - private fun onWebLoginError(onWebLoginError: LoginViewEvents2.OnWebLoginError) { - // Pop the backstack - supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) - - // And inform the user - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.dialog_title_error) - .setMessage(activity.getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode)) - .setPositiveButton(R.string.ok, null) - .show() - } - - /** - * Handle the SSO redirection here. - */ - override fun onNewIntent(intent: Intent?) { - intent?.data - ?.let { tryOrNull { it.getQueryParameter("loginToken") } } - ?.let { loginViewModel.handle(LoginAction2.LoginWithToken(it)) } - } - - private fun onRegistrationStageNotSupported() { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.app_name) - .setMessage(activity.getString(R.string.login_registration_not_supported)) - .setPositiveButton(R.string.yes) { _, _ -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginWebFragment2::class.java, - option = commonOption - ) - } - .setNegativeButton(R.string.no, null) - .show() - } - - private fun onLoginModeNotSupported(supportedTypes: List) { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.app_name) - .setMessage(activity.getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" })) - .setPositiveButton(R.string.yes) { _, _ -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginWebFragment2::class.java, - option = commonOption - ) - } - .setNegativeButton(R.string.no, null) - .show() - } - - private fun handleRegistrationNavigation(flowResult: FlowResult) { - // Complete all mandatory stages first - val mandatoryStage = flowResult.missingStages.firstOrNull { it.mandatory } - - if (mandatoryStage != null) { - doStage(mandatoryStage) - } else { - // Consider optional stages - val optionalStage = flowResult.missingStages.firstOrNull { !it.mandatory && it !is Stage.Dummy } - if (optionalStage == null) { - // Should not happen... - } else { - doStage(optionalStage) - } - } - } - - private fun doStage(stage: Stage) { - // Ensure there is no fragment for registration stage in the backstack - supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) - - when (stage) { - is Stage.ReCaptcha -> activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginCaptchaFragment2::class.java, - LoginCaptchaFragmentArgument(stage.publicKey), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is Stage.Email -> activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginGenericTextInputFormFragment2::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is Stage.Msisdn -> activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginGenericTextInputFormFragment2::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is Stage.Terms -> activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginTermsFragment2::class.java, - LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - else -> Unit // Should not happen - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt index 5a19732341..060472a2da 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt @@ -33,7 +33,7 @@ import javax.inject.Inject class OnboardingActivity : VectorBaseActivity(), UnlockedActivity { private val onboardingVariant by lifecycleAwareLazy { - onboardingVariantFactory.create(this, views = views, onboardingViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) + onboardingVariantFactory.create(this, views = views, onboardingViewModel = lazyViewModel()) } @Inject lateinit var onboardingVariantFactory: OnboardingVariantFactory diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt index 9837db8e91..fec0374afb 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt @@ -20,7 +20,6 @@ import im.vector.app.config.OnboardingVariant import im.vector.app.core.platform.ScreenOrientationLocker import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.features.VectorFeatures -import im.vector.app.features.login2.LoginViewModel2 import im.vector.app.features.onboarding.ftueauth.FtueAuthVariant import javax.inject.Inject @@ -33,7 +32,6 @@ class OnboardingVariantFactory @Inject constructor( activity: OnboardingActivity, views: ActivityLoginBinding, onboardingViewModel: Lazy, - loginViewModel2: Lazy ) = when (vectorFeatures.onboardingVariant()) { OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE") OnboardingVariant.FTUE_AUTH -> FtueAuthVariant( @@ -44,11 +42,5 @@ class OnboardingVariantFactory @Inject constructor( vectorFeatures = vectorFeatures, orientationLocker = orientationLocker ) - OnboardingVariant.LOGIN_2 -> Login2Variant( - views = views, - loginViewModel = loginViewModel2.value, - activity = activity, - supportFragmentManager = activity.supportFragmentManager - ) } }