removing login2 classes
This commit is contained in:
parent
62f7b40a3e
commit
19261ab2d0
|
@ -18,6 +18,5 @@ package im.vector.app.config
|
||||||
|
|
||||||
enum class OnboardingVariant {
|
enum class OnboardingVariant {
|
||||||
LEGACY,
|
LEGACY,
|
||||||
LOGIN_2,
|
|
||||||
FTUE_AUTH
|
FTUE_AUTH
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,24 +79,6 @@ import im.vector.app.features.login.LoginSplashFragment
|
||||||
import im.vector.app.features.login.LoginWaitForEmailFragment
|
import im.vector.app.features.login.LoginWaitForEmailFragment
|
||||||
import im.vector.app.features.login.LoginWebFragment
|
import im.vector.app.features.login.LoginWebFragment
|
||||||
import im.vector.app.features.login.terms.LoginTermsFragment
|
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.MatrixToRoomSpaceFragment
|
||||||
import im.vector.app.features.matrixto.MatrixToUserFragment
|
import im.vector.app.features.matrixto.MatrixToUserFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
|
||||||
|
@ -334,96 +316,6 @@ interface FragmentModule {
|
||||||
@FragmentKey(LoginWaitForEmailFragment::class)
|
@FragmentKey(LoginWaitForEmailFragment::class)
|
||||||
fun bindLoginWaitForEmailFragment(fragment: LoginWaitForEmailFragment): Fragment
|
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
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(FtueAuthLegacyStyleCaptchaFragment::class)
|
@FragmentKey(FtueAuthLegacyStyleCaptchaFragment::class)
|
||||||
|
|
|
@ -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.live.map.LiveLocationMapViewModel
|
||||||
import im.vector.app.features.location.preview.LocationPreviewViewModel
|
import im.vector.app.features.location.preview.LocationPreviewViewModel
|
||||||
import im.vector.app.features.login.LoginViewModel
|
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.matrixto.MatrixToBottomSheetViewModel
|
||||||
import im.vector.app.features.media.VectorAttachmentViewerViewModel
|
import im.vector.app.features.media.VectorAttachmentViewerViewModel
|
||||||
import im.vector.app.features.onboarding.OnboardingViewModel
|
import im.vector.app.features.onboarding.OnboardingViewModel
|
||||||
|
@ -456,21 +454,11 @@ interface MavericksViewModelModule {
|
||||||
@MavericksViewModelKey(MatrixToBottomSheetViewModel::class)
|
@MavericksViewModelKey(MatrixToBottomSheetViewModel::class)
|
||||||
fun matrixToBottomSheetViewModelFactory(factory: MatrixToBottomSheetViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun matrixToBottomSheetViewModelFactory(factory: MatrixToBottomSheetViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@MavericksViewModelKey(AccountCreatedViewModel::class)
|
|
||||||
fun accountCreatedViewModelFactory(factory: AccountCreatedViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(OnboardingViewModel::class)
|
@MavericksViewModelKey(OnboardingViewModel::class)
|
||||||
fun onboardingViewModelFactory(factory: OnboardingViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun onboardingViewModelFactory(factory: OnboardingViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@MavericksViewModelKey(LoginViewModel2::class)
|
|
||||||
fun loginViewModel2Factory(factory: LoginViewModel2.Factory): MavericksAssistedViewModelFactory<*, *>
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(LoginViewModel::class)
|
@MavericksViewModelKey(LoginViewModel::class)
|
||||||
|
|
|
@ -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<VB : ViewBinding> : VectorBaseFragment<VB>(), 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()
|
|
||||||
}
|
|
|
@ -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<VB : ViewBinding> : AbstractLoginFragment2<VB>() {
|
|
||||||
|
|
||||||
// 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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<SsoIdentityProvider>?
|
|
||||||
) : LoginAction2()
|
|
||||||
|
|
||||||
data class PostViewEvent(val viewEvent: LoginViewEvents2) : LoginAction2()
|
|
||||||
|
|
||||||
data class UserAcceptCertificate(val fingerprint: Fingerprint) : LoginAction2()
|
|
||||||
|
|
||||||
// Account customization is over
|
|
||||||
object Finish : LoginAction2()
|
|
||||||
}
|
|
|
@ -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<FragmentLoginCaptchaBinding>() {
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginSigninPassword2Binding>() {
|
|
||||||
|
|
||||||
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 }
|
|
||||||
}
|
|
|
@ -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<FragmentLoginSigninUsername2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginSignupPassword2Binding>() {
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginSignupUsername2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginSigninToAny2Binding>() {
|
|
||||||
|
|
||||||
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 }
|
|
||||||
}
|
|
|
@ -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<FragmentLoginGenericTextInputForm2Binding>() {
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginResetPassword2Binding>() {
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginResetPasswordMailConfirmation2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginResetPasswordSuccess2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginServerSelection2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginServerUrlForm2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginSplash2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginSsoOnly2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<String>) : 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()
|
|
||||||
}
|
|
|
@ -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<LoginViewState2, LoginAction2, LoginViewEvents2>(initialState) {
|
|
||||||
|
|
||||||
@AssistedFactory
|
|
||||||
interface Factory : MavericksAssistedViewModelFactory<LoginViewModel2, LoginViewState2> {
|
|
||||||
override fun create(initialState: LoginViewState2): LoginViewModel2
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : MavericksViewModelFactory<LoginViewModel2, LoginViewState2> 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<LoginProfileInfo> = Uninitialized,
|
|
||||||
|
|
||||||
// Network result
|
|
||||||
@PersistState
|
|
||||||
val loginMode: LoginMode = LoginMode.Unknown,
|
|
||||||
|
|
||||||
// From database
|
|
||||||
val knownCustomHomeServersUrls: List<String> = emptyList()
|
|
||||||
) : MavericksState {
|
|
||||||
|
|
||||||
// Pending user identifier
|
|
||||||
fun userIdentifier(): String {
|
|
||||||
return if (userName != null && MatrixPatterns.isUserId(userName)) {
|
|
||||||
userName
|
|
||||||
} else {
|
|
||||||
"@$userName:${homeServerUrlFromUser.toReducedUrl()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginWaitForEmail2Binding>() {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FragmentLoginWebBinding>() {
|
|
||||||
|
|
||||||
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:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 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/"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* .
|
|
||||||
* </pre>
|
|
||||||
* @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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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<FragmentLoginAccountCreatedBinding>(),
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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<AccountCreatedViewState, AccountCreatedAction, AccountCreatedViewEvents>(initialState) {
|
|
||||||
|
|
||||||
@AssistedFactory
|
|
||||||
interface Factory : MavericksAssistedViewModelFactory<AccountCreatedViewModel, AccountCreatedViewState> {
|
|
||||||
override fun create(initialState: AccountCreatedViewState): AccountCreatedViewModel
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : MavericksViewModelFactory<AccountCreatedViewModel, AccountCreatedViewState> 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<MatrixItem.UserItem> = Uninitialized,
|
|
||||||
val hasBeenModified: Boolean = false
|
|
||||||
) : MavericksState
|
|
|
@ -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<FragmentLoginTerms2Binding>(),
|
|
||||||
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<LocalizedFlowDataLoginTermsChecked>()
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -130,7 +130,6 @@ class DefaultNavigator @Inject constructor(
|
||||||
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
|
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
|
||||||
val intent = when (features.onboardingVariant()) {
|
val intent = when (features.onboardingVariant()) {
|
||||||
OnboardingVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig)
|
OnboardingVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig)
|
||||||
OnboardingVariant.LOGIN_2,
|
|
||||||
OnboardingVariant.FTUE_AUTH -> OnboardingActivity.newIntent(context, loginConfig)
|
OnboardingVariant.FTUE_AUTH -> OnboardingActivity.newIntent(context, loginConfig)
|
||||||
}
|
}
|
||||||
intent.addFlags(flags)
|
intent.addFlags(flags)
|
||||||
|
@ -140,7 +139,6 @@ class DefaultNavigator @Inject constructor(
|
||||||
override fun loginSSORedirect(context: Context, data: Uri?) {
|
override fun loginSSORedirect(context: Context, data: Uri?) {
|
||||||
val intent = when (features.onboardingVariant()) {
|
val intent = when (features.onboardingVariant()) {
|
||||||
OnboardingVariant.LEGACY -> LoginActivity.redirectIntent(context, data)
|
OnboardingVariant.LEGACY -> LoginActivity.redirectIntent(context, data)
|
||||||
OnboardingVariant.LOGIN_2,
|
|
||||||
OnboardingVariant.FTUE_AUTH -> OnboardingActivity.redirectIntent(context, data)
|
OnboardingVariant.FTUE_AUTH -> OnboardingActivity.redirectIntent(context, data)
|
||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
|
|
|
@ -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<ActivityLoginBinding>,
|
|
||||||
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<View?>(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<LoginConfig?>(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<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
|
||||||
// Disable transition of text
|
|
||||||
// activity.findViewById<View?>(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
|
||||||
// No transition here now actually
|
|
||||||
// activity.findViewById<View?>(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<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
|
||||||
// Disable transition of text
|
|
||||||
// activity.findViewById<View?>(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
|
||||||
// No transition here now actually
|
|
||||||
// activity.findViewById<View?>(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<String>) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -33,7 +33,7 @@ import javax.inject.Inject
|
||||||
class OnboardingActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedActivity {
|
class OnboardingActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedActivity {
|
||||||
|
|
||||||
private val onboardingVariant by lifecycleAwareLazy {
|
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
|
@Inject lateinit var onboardingVariantFactory: OnboardingVariantFactory
|
||||||
|
|
|
@ -20,7 +20,6 @@ import im.vector.app.config.OnboardingVariant
|
||||||
import im.vector.app.core.platform.ScreenOrientationLocker
|
import im.vector.app.core.platform.ScreenOrientationLocker
|
||||||
import im.vector.app.databinding.ActivityLoginBinding
|
import im.vector.app.databinding.ActivityLoginBinding
|
||||||
import im.vector.app.features.VectorFeatures
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.login2.LoginViewModel2
|
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthVariant
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthVariant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -33,7 +32,6 @@ class OnboardingVariantFactory @Inject constructor(
|
||||||
activity: OnboardingActivity,
|
activity: OnboardingActivity,
|
||||||
views: ActivityLoginBinding,
|
views: ActivityLoginBinding,
|
||||||
onboardingViewModel: Lazy<OnboardingViewModel>,
|
onboardingViewModel: Lazy<OnboardingViewModel>,
|
||||||
loginViewModel2: Lazy<LoginViewModel2>
|
|
||||||
) = when (vectorFeatures.onboardingVariant()) {
|
) = when (vectorFeatures.onboardingVariant()) {
|
||||||
OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE")
|
OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE")
|
||||||
OnboardingVariant.FTUE_AUTH -> FtueAuthVariant(
|
OnboardingVariant.FTUE_AUTH -> FtueAuthVariant(
|
||||||
|
@ -44,11 +42,5 @@ class OnboardingVariantFactory @Inject constructor(
|
||||||
vectorFeatures = vectorFeatures,
|
vectorFeatures = vectorFeatures,
|
||||||
orientationLocker = orientationLocker
|
orientationLocker = orientationLocker
|
||||||
)
|
)
|
||||||
OnboardingVariant.LOGIN_2 -> Login2Variant(
|
|
||||||
views = views,
|
|
||||||
loginViewModel = loginViewModel2.value,
|
|
||||||
activity = activity,
|
|
||||||
supportFragmentManager = activity.supportFragmentManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue