From 690fda180c339b4710b50d800cc74c3ae441b1a6 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 11 May 2022 18:29:44 +0100 Subject: [PATCH 01/11] providing dedicated reset action for resetting invalid deeplink homeserver - also fixes the usecase screen becoming stuck with an invalid homeserver deeplink --- .../features/onboarding/OnboardingAction.kt | 5 +- .../onboarding/OnboardingViewModel.kt | 15 ++-- .../FtueAuthSplashCarouselFragment.kt | 75 ++++++++++++++----- .../ftueauth/FtueAuthSplashFragment.kt | 7 +- .../ftueauth/FtueAuthUseCaseFragment.kt | 32 +++++++- 5 files changed, 101 insertions(+), 33 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index bef624ddc4..97a631dd1f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -25,8 +25,9 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.network.ssl.Fingerprint sealed interface OnboardingAction : VectorViewModelAction { - data class OnGetStarted(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction - data class OnIAlreadyHaveAnAccount(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction + object ResetDeeplinkConfig : OnboardingAction + data class OnGetStarted(val onboardingFlow: OnboardingFlow) : OnboardingAction + data class OnIAlreadyHaveAnAccount(val onboardingFlow: OnboardingFlow) : OnboardingAction data class UpdateServerType(val serverType: ServerType) : OnboardingAction diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 0bd61758bc..a7de3c157a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -30,6 +30,7 @@ import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.extensions.vectorStore import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureTrailingSlash import im.vector.app.features.VectorFeatures import im.vector.app.features.VectorOverrides @@ -110,7 +111,8 @@ class OnboardingViewModel @AssistedInject constructor( private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() - private val defaultHomeserverUrl = matrixOrgUrl + private val defaultHomeserverUrl: String + get() = loginConfig?.homeServerUrl?.ensureProtocol() ?: matrixOrgUrl private val registrationWizard: RegistrationWizard get() = authenticationService.getRegistrationWizard() @@ -132,8 +134,8 @@ class OnboardingViewModel @AssistedInject constructor( override fun handle(action: OnboardingAction) { when (action) { - is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) - is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) + is OnboardingAction.OnGetStarted -> handleSplashAction(action.onboardingFlow) + is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.onboardingFlow) is OnboardingAction.UpdateUseCase -> handleUpdateUseCase(action) OnboardingAction.ResetUseCase -> resetUseCase() is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) @@ -157,6 +159,7 @@ class OnboardingViewModel @AssistedInject constructor( OnboardingAction.SaveSelectedProfilePicture -> updateProfilePicture() is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent) OnboardingAction.StopEmailValidationCheck -> cancelWaitForEmailValidation() + OnboardingAction.ResetDeeplinkConfig -> loginConfig = null } } @@ -173,10 +176,7 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) { - if (resetConfig) { - loginConfig = null - } + private fun handleSplashAction(onboardingFlow: OnboardingFlow) { setState { copy(onboardingFlow = onboardingFlow) } return when (val config = loginConfig.toHomeserverConfig()) { @@ -422,7 +422,6 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleInitWith(action: OnboardingAction.InitWith) { loginConfig = action.loginConfig - // If there is a pending email validation continue on this step try { if (registrationWizard.isRegistrationStarted) { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt index 30416bde9e..8306589e89 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt @@ -17,6 +17,9 @@ package im.vector.app.features.onboarding.ftueauth import android.annotation.SuppressLint +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -41,8 +44,7 @@ import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.failure.Failure -import java.net.UnknownHostException +import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import javax.inject.Inject private const val CAROUSEL_ROTATION_DELAY_MS = 5000L @@ -128,11 +130,11 @@ class FtueAuthSplashCarouselFragment @Inject constructor( private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) { val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp - viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow)) + viewModel.handle(OnboardingAction.OnGetStarted(onboardingFlow = getStartedFlow)) } private fun alreadyHaveAnAccount() { - viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(resetLoginConfig = false, onboardingFlow = OnboardingFlow.SignIn)) + viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.SignIn)) } override fun resetViewModel() { @@ -140,21 +142,56 @@ class FtueAuthSplashCarouselFragment @Inject constructor( } override fun onError(throwable: Throwable) { - if (throwable is Failure.NetworkConnection && - throwable.ioException is UnknownHostException) { - // Invalid homeserver from URL config - val url = viewModel.getInitialHomeServerUrl().orEmpty() - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url)) - .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> - val flow = withState(viewModel) { it.onboardingFlow } ?: OnboardingFlow.SignInSignUp - viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = true, flow)) - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } else { - super.onError(throwable) + when { + requireContext().inferNoConnectivity() -> super.onError(throwable) + throwable.isHomeserverUnavailable() -> { + val url = viewModel.getInitialHomeServerUrl().orEmpty() + homeserverUnavailableDialog(url) { onContinueFlowWithLoginConfigReset() } + } + else -> super.onError(throwable) } } + + private fun onContinueFlowWithLoginConfigReset() { + viewModel.handle(OnboardingAction.ResetDeeplinkConfig) + when (val flow = withState(viewModel) { it.onboardingFlow } ?: OnboardingFlow.SignInSignUp) { + OnboardingFlow.SignIn -> if (vectorFeatures.isOnboardingCombinedLoginEnabled()) { + viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(flow)) + } else { + viewModel.handle(OnboardingAction.OnGetStarted(flow)) + } + else -> viewModel.handle(OnboardingAction.OnGetStarted(flow)) + } + } + + private fun homeserverUnavailableDialog(url: String, action: () -> Unit) { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url)) + .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> action() } + .setNegativeButton(R.string.action_cancel, null) + .show() + } +} + +fun Context.inferNoConnectivity(): Boolean { + var networkAvailable = false + + val connectivityManager: ConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val network = connectivityManager.activeNetwork + val networkCapabilities = connectivityManager.getNetworkCapabilities(network) + + when { + networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> { + networkAvailable = true + } + networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true -> { + networkAvailable = true + } + networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true -> { + networkAvailable = true + } + } + + return !networkAvailable } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt index 2fa3b52706..c961986fd2 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt @@ -75,11 +75,11 @@ class FtueAuthSplashFragment @Inject constructor( private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) { val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp - viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow)) + viewModel.handle(OnboardingAction.OnGetStarted(onboardingFlow = getStartedFlow)) } private fun alreadyHaveAnAccount() { - viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(resetLoginConfig = false, onboardingFlow = OnboardingFlow.SignIn)) + viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.SignIn)) } override fun resetViewModel() { @@ -96,7 +96,8 @@ class FtueAuthSplashFragment @Inject constructor( .setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url)) .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> val flow = withState(viewModel) { it.onboardingFlow } ?: OnboardingFlow.SignInSignUp - viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = true, flow)) + viewModel.handle(OnboardingAction.ResetDeeplinkConfig) + viewModel.handle(OnboardingAction.OnGetStarted(flow)) } .setNegativeButton(R.string.action_cancel, null) .show() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt index 5325b25e93..41675e20ca 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -28,6 +28,8 @@ import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat +import com.airbnb.mvrx.withState +import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.getResTintedDrawable import im.vector.app.core.extensions.getTintedDrawable @@ -38,13 +40,14 @@ import im.vector.app.features.login.ServerType import im.vector.app.features.onboarding.FtueUseCase import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.themes.ThemeProvider +import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import javax.inject.Inject private const val DARK_MODE_ICON_BACKGROUND_ALPHA = 0.30f private const val LIGHT_MODE_ICON_BACKGROUND_ALPHA = 0.15f class FtueAuthUseCaseFragment @Inject constructor( - private val themeProvider: ThemeProvider + private val themeProvider: ThemeProvider, ) : AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthUseCaseBinding { @@ -111,4 +114,31 @@ class FtueAuthUseCaseFragment @Inject constructor( val whiteLayer = context.getTintedDrawable(R.drawable.bg_feature_icon, Color.WHITE) return LayerDrawable(arrayOf(whiteLayer, iconBackground, ContextCompat.getDrawable(context, icon))) } + + override fun onError(throwable: Throwable) { + when { + requireContext().inferNoConnectivity() -> super.onError(throwable) + throwable.isHomeserverUnavailable() -> { + val url = viewModel.getInitialHomeServerUrl().orEmpty() + homeserverUnavailableDialog(url) { onContinueFlowWithLoginConfigReset() } + } + else -> super.onError(throwable) + } + } + + private fun onContinueFlowWithLoginConfigReset() { + viewModel.handle(OnboardingAction.ResetDeeplinkConfig) + withState(viewModel) { it.useCase }?.let { + viewModel.handle(OnboardingAction.UpdateUseCase(it)) + } + } + + private fun homeserverUnavailableDialog(url: String, action: () -> Unit) { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url)) + .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> action() } + .setNegativeButton(R.string.action_cancel, null) + .show() + } } From b8418f97dc674adc68a6ef805a61b43bb38e1cac Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 12 May 2022 10:41:10 +0100 Subject: [PATCH 02/11] extracting server selection branches to their own functions --- .../onboarding/OnboardingViewModel.kt | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index a7de3c157a..7970977b95 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -649,42 +649,11 @@ class OnboardingViewModel @AssistedInject constructor( } when (trigger) { - is OnboardingAction.HomeServerChange.EditHomeServer -> { - when (awaitState().onboardingFlow) { - OnboardingFlow.SignUp -> internalRegisterAction(RegisterAction.StartRegistration) { - updateServerSelection(config, serverTypeOverride, authResult) - _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited) - } - OnboardingFlow.SignIn -> { - updateServerSelection(config, serverTypeOverride, authResult) - _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited) - } - else -> throw IllegalArgumentException("developer error") - } - } is OnboardingAction.HomeServerChange.SelectHomeServer -> { - updateServerSelection(config, serverTypeOverride, authResult) - if (authResult.selectedHomeserver.preferredLoginMode.supportsSignModeScreen()) { - when (awaitState().onboardingFlow) { - OnboardingFlow.SignIn -> { - updateSignMode(SignMode.SignIn) - when (vectorFeatures.isOnboardingCombinedLoginEnabled()) { - true -> _viewEvents.post(OnboardingViewEvents.OpenCombinedLogin) - false -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn)) - } - } - OnboardingFlow.SignUp -> { - updateSignMode(SignMode.SignUp) - internalRegisterAction(RegisterAction.StartRegistration, ::emitFlowResultViewEvent) - } - OnboardingFlow.SignInSignUp, - null -> { - _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) - } - } - } else { - _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) - } + onHomeServerSelected(config, serverTypeOverride, authResult) + } + is OnboardingAction.HomeServerChange.EditHomeServer -> { + onHomeServerEdited(config, serverTypeOverride, authResult) } else -> { updateServerSelection(config, serverTypeOverride, authResult) @@ -693,6 +662,45 @@ class OnboardingViewModel @AssistedInject constructor( } } + private suspend fun onHomeServerSelected(config: HomeServerConnectionConfig, serverTypeOverride: ServerType?, authResult: StartAuthenticationResult) { + updateServerSelection(config, serverTypeOverride, authResult) + if (authResult.selectedHomeserver.preferredLoginMode.supportsSignModeScreen()) { + when (awaitState().onboardingFlow) { + OnboardingFlow.SignIn -> { + updateSignMode(SignMode.SignIn) + when (vectorFeatures.isOnboardingCombinedLoginEnabled()) { + true -> _viewEvents.post(OnboardingViewEvents.OpenCombinedLogin) + false -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn)) + } + } + OnboardingFlow.SignUp -> { + updateSignMode(SignMode.SignUp) + internalRegisterAction(RegisterAction.StartRegistration, ::emitFlowResultViewEvent) + } + OnboardingFlow.SignInSignUp, + null -> { + _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) + } + } + } else { + _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) + } + } + + private suspend fun onHomeServerEdited(config: HomeServerConnectionConfig, serverTypeOverride: ServerType?, authResult: StartAuthenticationResult) { + when (awaitState().onboardingFlow) { + OnboardingFlow.SignUp -> internalRegisterAction(RegisterAction.StartRegistration) { + updateServerSelection(config, serverTypeOverride, authResult) + _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited) + } + OnboardingFlow.SignIn -> { + updateServerSelection(config, serverTypeOverride, authResult) + _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited) + } + else -> throw IllegalArgumentException("developer error") + } + } + private fun updateServerSelection(config: HomeServerConnectionConfig, serverTypeOverride: ServerType?, authResult: StartAuthenticationResult) { setState { copy( From 59afb5cf4cfd74d2e87d1f4fd6a4ecb89f86bee4 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 12 May 2022 11:24:10 +0100 Subject: [PATCH 03/11] downscoping the possible action types when selecting homeservers --- .../features/onboarding/OnboardingAction.kt | 3 +- .../onboarding/OnboardingViewModel.kt | 60 +++++++++---------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index 97a631dd1f..5d5dc9b6c0 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.network.ssl.Fingerprint sealed interface OnboardingAction : VectorViewModelAction { - object ResetDeeplinkConfig : OnboardingAction data class OnGetStarted(val onboardingFlow: OnboardingFlow) : OnboardingAction data class OnIAlreadyHaveAnAccount(val onboardingFlow: OnboardingFlow) : OnboardingAction @@ -59,7 +58,7 @@ sealed interface OnboardingAction : VectorViewModelAction { // Reset actions sealed interface ResetAction : OnboardingAction - + object ResetDeeplinkConfig : ResetAction object ResetHomeServerType : ResetAction object ResetHomeServerUrl : ResetAction object ResetSignMode : ResetAction diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 7970977b95..f5131e1c08 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -111,8 +111,7 @@ class OnboardingViewModel @AssistedInject constructor( private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() - private val defaultHomeserverUrl: String - get() = loginConfig?.homeServerUrl?.ensureProtocol() ?: matrixOrgUrl + private val defaultHomeserverUrl = matrixOrgUrl private val registrationWizard: RegistrationWizard get() = authenticationService.getRegistrationWizard() @@ -159,7 +158,6 @@ class OnboardingViewModel @AssistedInject constructor( OnboardingAction.SaveSelectedProfilePicture -> updateProfilePicture() is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent) OnboardingAction.StopEmailValidationCheck -> cancelWaitForEmailValidation() - OnboardingAction.ResetDeeplinkConfig -> loginConfig = null } } @@ -178,21 +176,7 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleSplashAction(onboardingFlow: OnboardingFlow) { setState { copy(onboardingFlow = onboardingFlow) } - - return when (val config = loginConfig.toHomeserverConfig()) { - null -> continueToPageAfterSplash(onboardingFlow) - else -> startAuthenticationFlow(trigger = null, config, ServerType.Other) - } - } - - private fun LoginConfig?.toHomeserverConfig(): HomeServerConnectionConfig? { - return this?.homeServerUrl?.takeIf { it.isNotEmpty() }?.let { url -> - homeServerConnectionConfigFactory.create(url).also { - if (it == null) { - Timber.w("Url from config url was invalid: $url") - } - } - } + continueToPageAfterSplash(onboardingFlow) } private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) { @@ -206,10 +190,21 @@ class OnboardingViewModel @AssistedInject constructor( } ) } - OnboardingFlow.SignIn -> if (vectorFeatures.isOnboardingCombinedLoginEnabled()) { - handle(OnboardingAction.HomeServerChange.SelectHomeServer(defaultHomeserverUrl)) - } else _viewEvents.post(OnboardingViewEvents.OpenServerSelection) - OnboardingFlow.SignInSignUp -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection) + OnboardingFlow.SignIn -> when { + vectorFeatures.isOnboardingCombinedLoginEnabled() -> { + handle(OnboardingAction.HomeServerChange.SelectHomeServer(deeplinkOrDefaultHomeserverUrl())) + } + else -> openServerSelectionOrDeeplinkToOther() + } + + OnboardingFlow.SignInSignUp -> openServerSelectionOrDeeplinkToOther() + } + } + + private fun openServerSelectionOrDeeplinkToOther() { + when (loginConfig) { + null -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection) + else -> handleHomeserverChange(OnboardingAction.HomeServerChange.SelectHomeServer(deeplinkOrDefaultHomeserverUrl()), ServerType.Other) } } @@ -220,7 +215,7 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.HomeServerChange.SelectHomeServer -> { currentHomeServerConnectionConfig ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { startAuthenticationFlow(finalLastAction, it) } + ?.let { startAuthenticationFlow(finalLastAction, it, serverTypeOverride = null) } } is AuthenticateAction.LoginDirect -> handleDirectLogin( @@ -374,6 +369,7 @@ class OnboardingViewModel @AssistedInject constructor( ) } } + OnboardingAction.ResetDeeplinkConfig -> loginConfig = null } } @@ -394,11 +390,13 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleUpdateUseCase(action: OnboardingAction.UpdateUseCase) { setState { copy(useCase = action.useCase) } when (vectorFeatures.isOnboardingCombinedRegisterEnabled()) { - true -> handle(OnboardingAction.HomeServerChange.SelectHomeServer(defaultHomeserverUrl)) + true -> handle(OnboardingAction.HomeServerChange.SelectHomeServer(deeplinkOrDefaultHomeserverUrl())) false -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection) } } + private fun deeplinkOrDefaultHomeserverUrl() = loginConfig?.homeServerUrl?.ensureProtocol() ?: defaultHomeserverUrl + private fun resetUseCase() { setState { copy(useCase = null) } } @@ -610,20 +608,20 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun handleHomeserverChange(action: OnboardingAction.HomeServerChange) { + private fun handleHomeserverChange(action: OnboardingAction.HomeServerChange, serverTypeOverride: ServerType? = null) { val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl) if (homeServerConnectionConfig == null) { // This is invalid _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) } else { - startAuthenticationFlow(action, homeServerConnectionConfig) + startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride) } } private fun startAuthenticationFlow( - trigger: OnboardingAction?, + trigger: OnboardingAction.HomeServerChange, homeServerConnectionConfig: HomeServerConnectionConfig, - serverTypeOverride: ServerType? = null + serverTypeOverride: ServerType? ) { currentHomeServerConnectionConfig = homeServerConnectionConfig @@ -638,7 +636,7 @@ class OnboardingViewModel @AssistedInject constructor( } private suspend fun onAuthenticationStartedSuccess( - trigger: OnboardingAction?, + trigger: OnboardingAction.HomeServerChange, config: HomeServerConnectionConfig, authResult: StartAuthenticationResult, serverTypeOverride: ServerType? @@ -655,10 +653,6 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.HomeServerChange.EditHomeServer -> { onHomeServerEdited(config, serverTypeOverride, authResult) } - else -> { - updateServerSelection(config, serverTypeOverride, authResult) - _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) - } } } From 100aa2402114ea1829c8875d11d5cd792b50c1c1 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 12 May 2022 11:26:26 +0100 Subject: [PATCH 04/11] adding helper for inferring if the device has connectivity, this helps with breaking down UnknownHost exceptioncauses and shouldn't be used for checking offline status --- .../im/vector/app/core/extensions/Context.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt index 0f785e43a3..35bc01ef29 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt @@ -18,7 +18,10 @@ package im.vector.app.core.extensions import android.content.Context import android.graphics.drawable.Drawable +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.net.Uri +import android.os.Build import android.text.Spannable import android.text.SpannableString import android.text.style.ImageSpan @@ -77,3 +80,30 @@ val Context.dataStoreProvider: (String) -> DataStore by dataStorePr fun Context.safeOpenOutputStream(uri: Uri): OutputStream? { return contentResolver.openOutputStream(uri, "wt") } + +/** + * Checks for an active connection to infer if the device is offline. + * This is useful for breaking down UnknownHost exceptions and should not be used to determine if a valid connection is present + * + * @return true if no active connection is found + */ +@Suppress("deprecation") +fun Context.inferNoConnectivity(): Boolean { + val connectivityManager: ConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { + val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + when { + networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> false + networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true -> false + networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true -> false + else -> true + } + } else { + when (connectivityManager.activeNetworkInfo?.type) { + ConnectivityManager.TYPE_WIFI -> false + ConnectivityManager.TYPE_MOBILE -> false + ConnectivityManager.TYPE_VPN -> false + else -> true + } + } +} From ea7df9b6735ba087f11c35133b2a59469dc8bc0e Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 12 May 2022 11:49:46 +0100 Subject: [PATCH 05/11] lifting unavailable homeserver rendering to the activity/ftuevariant - the viewmodel is now responsible for inferring connectivity errors and providing a retry action --- .../features/onboarding/OnboardingAction.kt | 8 ++- .../onboarding/OnboardingViewEvents.kt | 1 + .../onboarding/OnboardingViewModel.kt | 29 +++++++-- .../FtueAuthSplashCarouselFragment.kt | 64 +------------------ .../ftueauth/FtueAuthSplashFragment.kt | 28 +------- .../ftueauth/FtueAuthUseCaseFragment.kt | 32 +--------- .../onboarding/ftueauth/FtueAuthVariant.kt | 21 +++++- .../onboarding/ftueauth/FtueExtensions.kt | 4 ++ 8 files changed, 58 insertions(+), 129 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index 5d5dc9b6c0..ce387d29fa 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -25,8 +25,12 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.network.ssl.Fingerprint sealed interface OnboardingAction : VectorViewModelAction { - data class OnGetStarted(val onboardingFlow: OnboardingFlow) : OnboardingAction - data class OnIAlreadyHaveAnAccount(val onboardingFlow: OnboardingFlow) : OnboardingAction + sealed interface SplashAction: OnboardingAction { + val onboardingFlow: OnboardingFlow + + data class OnGetStarted(override val onboardingFlow: OnboardingFlow) : SplashAction + data class OnIAlreadyHaveAnAccount(override val onboardingFlow: OnboardingFlow) : SplashAction + } data class UpdateServerType(val serverType: ServerType) : OnboardingAction diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index 5dbcd162f3..5d6e7005c4 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.auth.registration.FlowResult sealed class OnboardingViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : OnboardingViewEvents() data class Failure(val throwable: Throwable) : OnboardingViewEvents() + data class DeeplinkAuthenticationFailure(val retryAction: OnboardingAction) : OnboardingViewEvents() data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : OnboardingViewEvents() object OutdatedHomeserver : OnboardingViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index f5131e1c08..2a5f57ac70 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -27,6 +27,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.cancelCurrentOnSet import im.vector.app.core.extensions.configureAndStart +import im.vector.app.core.extensions.inferNoConnectivity import im.vector.app.core.extensions.vectorStore import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -56,6 +57,7 @@ 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.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.session.Session import timber.log.Timber import java.util.UUID @@ -133,8 +135,7 @@ class OnboardingViewModel @AssistedInject constructor( override fun handle(action: OnboardingAction) { when (action) { - is OnboardingAction.OnGetStarted -> handleSplashAction(action.onboardingFlow) - is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.onboardingFlow) + is OnboardingAction.SplashAction -> handleSplashAction(action) is OnboardingAction.UpdateUseCase -> handleUpdateUseCase(action) OnboardingAction.ResetUseCase -> resetUseCase() is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) @@ -174,9 +175,9 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun handleSplashAction(onboardingFlow: OnboardingFlow) { - setState { copy(onboardingFlow = onboardingFlow) } - continueToPageAfterSplash(onboardingFlow) + private fun handleSplashAction(action: OnboardingAction.SplashAction) { + setState { copy(onboardingFlow = action.onboardingFlow) } + continueToPageAfterSplash(action.onboardingFlow) } private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) { @@ -629,12 +630,28 @@ class OnboardingViewModel @AssistedInject constructor( setState { copy(isLoading = true) } runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold( onSuccess = { onAuthenticationStartedSuccess(trigger, homeServerConnectionConfig, it, serverTypeOverride) }, - onFailure = { _viewEvents.post(OnboardingViewEvents.Failure(it)) } + onFailure = { onAuthenticationStartError(it, trigger) } ) setState { copy(isLoading = false) } } } + private fun onAuthenticationStartError(it: Throwable, trigger: OnboardingAction.HomeServerChange) { + when { + it.isHomeserverUnavailable() && applicationContext.inferNoConnectivity() -> _viewEvents.post( + OnboardingViewEvents.Failure(it) + ) + it.isHomeserverUnavailable() && trigger is OnboardingAction.HomeServerChange.SelectHomeServer -> _viewEvents.post( + OnboardingViewEvents.DeeplinkAuthenticationFailure(retryAction = trigger.resetToDefaultUrl()) + ) + else -> _viewEvents.post( + OnboardingViewEvents.Failure(it) + ) + } + } + + private fun OnboardingAction.HomeServerChange.SelectHomeServer.resetToDefaultUrl() = copy(homeServerUrl = defaultHomeserverUrl) + private suspend fun onAuthenticationStartedSuccess( trigger: OnboardingAction.HomeServerChange, config: HomeServerConnectionConfig, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt index 8306589e89..0d86c4cd24 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt @@ -17,9 +17,6 @@ package im.vector.app.features.onboarding.ftueauth import android.annotation.SuppressLint -import android.content.Context -import android.net.ConnectivityManager -import android.net.NetworkCapabilities import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -29,8 +26,6 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.viewpager2.widget.ViewPager2 -import com.airbnb.mvrx.withState -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.BuildConfig import im.vector.app.R @@ -44,7 +39,6 @@ import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import javax.inject.Inject private const val CAROUSEL_ROTATION_DELAY_MS = 5000L @@ -130,68 +124,14 @@ class FtueAuthSplashCarouselFragment @Inject constructor( private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) { val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp - viewModel.handle(OnboardingAction.OnGetStarted(onboardingFlow = getStartedFlow)) + viewModel.handle(OnboardingAction.SplashAction.OnGetStarted(onboardingFlow = getStartedFlow)) } private fun alreadyHaveAnAccount() { - viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.SignIn)) + viewModel.handle(OnboardingAction.SplashAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.SignIn)) } override fun resetViewModel() { // Nothing to do } - - override fun onError(throwable: Throwable) { - when { - requireContext().inferNoConnectivity() -> super.onError(throwable) - throwable.isHomeserverUnavailable() -> { - val url = viewModel.getInitialHomeServerUrl().orEmpty() - homeserverUnavailableDialog(url) { onContinueFlowWithLoginConfigReset() } - } - else -> super.onError(throwable) - } - } - - private fun onContinueFlowWithLoginConfigReset() { - viewModel.handle(OnboardingAction.ResetDeeplinkConfig) - when (val flow = withState(viewModel) { it.onboardingFlow } ?: OnboardingFlow.SignInSignUp) { - OnboardingFlow.SignIn -> if (vectorFeatures.isOnboardingCombinedLoginEnabled()) { - viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(flow)) - } else { - viewModel.handle(OnboardingAction.OnGetStarted(flow)) - } - else -> viewModel.handle(OnboardingAction.OnGetStarted(flow)) - } - } - - private fun homeserverUnavailableDialog(url: String, action: () -> Unit) { - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url)) - .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> action() } - .setNegativeButton(R.string.action_cancel, null) - .show() - } -} - -fun Context.inferNoConnectivity(): Boolean { - var networkAvailable = false - - val connectivityManager: ConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val network = connectivityManager.activeNetwork - val networkCapabilities = connectivityManager.getNetworkCapabilities(network) - - when { - networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> { - networkAvailable = true - } - networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true -> { - networkAvailable = true - } - networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true -> { - networkAvailable = true - } - } - - return !networkAvailable } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt index c961986fd2..cd1e4b2714 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt @@ -22,8 +22,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible -import com.airbnb.mvrx.withState -import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.databinding.FragmentFtueAuthSplashBinding @@ -31,8 +29,6 @@ import im.vector.app.features.VectorFeatures import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.settings.VectorPreferences -import org.matrix.android.sdk.api.failure.Failure -import java.net.UnknownHostException import javax.inject.Inject /** @@ -75,34 +71,14 @@ class FtueAuthSplashFragment @Inject constructor( private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) { val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp - viewModel.handle(OnboardingAction.OnGetStarted(onboardingFlow = getStartedFlow)) + viewModel.handle(OnboardingAction.SplashAction.OnGetStarted(onboardingFlow = getStartedFlow)) } private fun alreadyHaveAnAccount() { - viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.SignIn)) + viewModel.handle(OnboardingAction.SplashAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.SignIn)) } override fun resetViewModel() { // Nothing to do } - - override fun onError(throwable: Throwable) { - if (throwable is Failure.NetworkConnection && - throwable.ioException is UnknownHostException) { - // Invalid homeserver from URL config - val url = viewModel.getInitialHomeServerUrl().orEmpty() - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url)) - .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> - val flow = withState(viewModel) { it.onboardingFlow } ?: OnboardingFlow.SignInSignUp - viewModel.handle(OnboardingAction.ResetDeeplinkConfig) - viewModel.handle(OnboardingAction.OnGetStarted(flow)) - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } else { - super.onError(throwable) - } - } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt index 41675e20ca..35439a794e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -28,8 +28,6 @@ import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat -import com.airbnb.mvrx.withState -import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.getResTintedDrawable import im.vector.app.core.extensions.getTintedDrawable @@ -40,7 +38,6 @@ import im.vector.app.features.login.ServerType import im.vector.app.features.onboarding.FtueUseCase import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.themes.ThemeProvider -import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import javax.inject.Inject private const val DARK_MODE_ICON_BACKGROUND_ALPHA = 0.30f @@ -107,38 +104,11 @@ class FtueAuthUseCaseFragment @Inject constructor( private fun createIcon(@ColorRes tint: Int, icon: Int, isLightMode: Boolean): Drawable { val context = requireContext() val alpha = when (isLightMode) { - true -> LIGHT_MODE_ICON_BACKGROUND_ALPHA + true -> LIGHT_MODE_ICON_BACKGROUND_ALPHA false -> DARK_MODE_ICON_BACKGROUND_ALPHA } val iconBackground = context.getResTintedDrawable(R.drawable.bg_feature_icon, tint, alpha = alpha) val whiteLayer = context.getTintedDrawable(R.drawable.bg_feature_icon, Color.WHITE) return LayerDrawable(arrayOf(whiteLayer, iconBackground, ContextCompat.getDrawable(context, icon))) } - - override fun onError(throwable: Throwable) { - when { - requireContext().inferNoConnectivity() -> super.onError(throwable) - throwable.isHomeserverUnavailable() -> { - val url = viewModel.getInitialHomeServerUrl().orEmpty() - homeserverUnavailableDialog(url) { onContinueFlowWithLoginConfigReset() } - } - else -> super.onError(throwable) - } - } - - private fun onContinueFlowWithLoginConfigReset() { - viewModel.handle(OnboardingAction.ResetDeeplinkConfig) - withState(viewModel) { it.useCase }?.let { - viewModel.handle(OnboardingAction.UpdateUseCase(it)) - } - } - - private fun homeserverUnavailableDialog(url: String, action: () -> Unit) { - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url)) - .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> action() } - .setNegativeButton(R.string.action_cancel, null) - .show() - } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 5ad6b7e78d..7a3729ac69 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -227,11 +227,28 @@ class FtueAuthVariant( option = commonOption ) } - OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack() - OnboardingViewEvents.OpenCombinedLogin -> onStartCombinedLogin() + OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack() + OnboardingViewEvents.OpenCombinedLogin -> onStartCombinedLogin() + is OnboardingViewEvents.DeeplinkAuthenticationFailure -> onDeeplinkedHomeserverUnavailable(viewEvents) } } + private fun onDeeplinkedHomeserverUnavailable(viewEvents: OnboardingViewEvents.DeeplinkAuthenticationFailure) { + showHomeserverUnavailableDialog(onboardingViewModel.getInitialHomeServerUrl().orEmpty()) { + onboardingViewModel.handle(OnboardingAction.ResetDeeplinkConfig) + onboardingViewModel.handle(viewEvents.retryAction) + } + } + + private fun showHomeserverUnavailableDialog(url: String, action: () -> Unit) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.dialog_title_error) + .setMessage(activity.getString(R.string.login_error_homeserver_from_url_not_found, url)) + .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> action() } + .setNegativeButton(R.string.action_cancel, null) + .show() + } + private fun onStartCombinedLogin() { addRegistrationStageFragmentToBackstack(FtueAuthCombinedLoginFragment::class.java) } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt index 8d63fbf547..5228e289bc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt @@ -17,13 +17,17 @@ package im.vector.app.features.onboarding.ftueauth import android.widget.Button +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputLayout +import im.vector.app.R import im.vector.app.core.extensions.hasContentFlow +import im.vector.app.core.extensions.inferNoConnectivity import im.vector.app.features.login.SignMode import im.vector.app.features.onboarding.OnboardingAction import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.failure.isHomeserverUnavailable fun SignMode.toAuthenticateAction(login: String, password: String, initialDeviceName: String): OnboardingAction.AuthenticateAction { return when (this) { From 797e0ee7066787dc31ec8253aed0cae4a177c8aa Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 12 May 2022 12:33:23 +0100 Subject: [PATCH 06/11] creating a build meta abstraction for allowing testing classes with build version checks --- .../im/vector/app/core/di/SingletonModule.kt | 6 +++++ .../im/vector/app/core/resources/BuildMeta.kt | 23 +++++++++++++++++++ .../app/test/fixtures/BuildMetaFixture.kt | 22 ++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/core/resources/BuildMeta.kt create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/BuildMetaFixture.kt diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index 2945ae7d87..22ce4ee0ce 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -33,6 +33,7 @@ import im.vector.app.config.analyticsConfig import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.resources.BuildMeta import im.vector.app.core.time.Clock import im.vector.app.core.time.DefaultClock import im.vector.app.features.analytics.AnalyticsConfig @@ -185,4 +186,9 @@ object VectorStaticModule { fun providesAnalyticsConfig(): AnalyticsConfig { return analyticsConfig } + + + @Provides + @Singleton + fun providesBuildMeta() = BuildMeta() } diff --git a/vector/src/main/java/im/vector/app/core/resources/BuildMeta.kt b/vector/src/main/java/im/vector/app/core/resources/BuildMeta.kt new file mode 100644 index 0000000000..14d97e4c8f --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/resources/BuildMeta.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 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.core.resources + +import android.os.Build + +data class BuildMeta( + val sdkInt: Int = Build.VERSION.SDK_INT +) diff --git a/vector/src/test/java/im/vector/app/test/fixtures/BuildMetaFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/BuildMetaFixture.kt new file mode 100644 index 0000000000..b0e6b1dd51 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fixtures/BuildMetaFixture.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 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.test.fixtures + +import android.os.Build +import im.vector.app.core.resources.BuildMeta + +fun aBuildMeta() = BuildMeta(Build.VERSION_CODES.O) From 75d038b05863422c16eee51141dfc76feadf359f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 12 May 2022 12:35:13 +0100 Subject: [PATCH 07/11] adding test case around invalid deeplinks within the onboarding flow --- .../im/vector/app/core/di/SingletonModule.kt | 1 - .../im/vector/app/core/extensions/Context.kt | 10 +++-- .../onboarding/OnboardingViewModel.kt | 18 +++++--- .../onboarding/ftueauth/FtueExtensions.kt | 4 -- .../onboarding/OnboardingViewModelTest.kt | 29 +++++++++++- .../app/test/fakes/FakeConnectivityManager.kt | 44 +++++++++++++++++++ .../im/vector/app/test/fakes/FakeContext.kt | 18 ++++++++ .../app/test/fakes/FakeNetworkCapabilities.kt | 32 ++++++++++++++ .../FakeStartAuthenticationFlowUseCase.kt | 6 +++ .../app/test/fixtures/FailureFixture.kt | 3 ++ 10 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeConnectivityManager.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeNetworkCapabilities.kt diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index 22ce4ee0ce..44f8bb1b3e 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -187,7 +187,6 @@ object VectorStaticModule { return analyticsConfig } - @Provides @Singleton fun providesBuildMeta() = BuildMeta() diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt index 35bc01ef29..81844a403b 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt @@ -16,6 +16,7 @@ package im.vector.app.core.extensions +import android.annotation.SuppressLint import android.content.Context import android.graphics.drawable.Drawable import android.net.ConnectivityManager @@ -30,11 +31,13 @@ import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.FloatRange import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import dagger.hilt.EntryPoints import im.vector.app.core.datastore.dataStoreProvider import im.vector.app.core.di.SingletonEntryPoint +import im.vector.app.core.resources.BuildMeta import java.io.OutputStream import kotlin.math.roundToInt @@ -88,9 +91,10 @@ fun Context.safeOpenOutputStream(uri: Uri): OutputStream? { * @return true if no active connection is found */ @Suppress("deprecation") -fun Context.inferNoConnectivity(): Boolean { - val connectivityManager: ConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { +@SuppressLint("NewApi") // false positive +fun Context.inferNoConnectivity(buildMeta: BuildMeta): Boolean { + val connectivityManager = getSystemService()!! + return if (buildMeta.sdkInt > Build.VERSION_CODES.M) { val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) when { networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> false diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 2a5f57ac70..5af4fa138b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -30,6 +30,7 @@ import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.extensions.inferNoConnectivity import im.vector.app.core.extensions.vectorStore import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureTrailingSlash @@ -81,7 +82,8 @@ class OnboardingViewModel @AssistedInject constructor( private val registrationActionHandler: RegistrationActionHandler, private val directLoginUseCase: DirectLoginUseCase, private val startAuthenticationFlowUseCase: StartAuthenticationFlowUseCase, - private val vectorOverrides: VectorOverrides + private val vectorOverrides: VectorOverrides, + private val buildMeta: BuildMeta ) : VectorViewModel(initialState) { @AssistedFactory @@ -638,18 +640,24 @@ class OnboardingViewModel @AssistedInject constructor( private fun onAuthenticationStartError(it: Throwable, trigger: OnboardingAction.HomeServerChange) { when { - it.isHomeserverUnavailable() && applicationContext.inferNoConnectivity() -> _viewEvents.post( + it.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(buildMeta) -> _viewEvents.post( OnboardingViewEvents.Failure(it) ) - it.isHomeserverUnavailable() && trigger is OnboardingAction.HomeServerChange.SelectHomeServer -> _viewEvents.post( - OnboardingViewEvents.DeeplinkAuthenticationFailure(retryAction = trigger.resetToDefaultUrl()) + deeplinkUrlIsUnavailable(it, trigger) -> _viewEvents.post( + OnboardingViewEvents.DeeplinkAuthenticationFailure( + retryAction = (trigger as OnboardingAction.HomeServerChange.SelectHomeServer).resetToDefaultUrl() + ) ) - else -> _viewEvents.post( + else -> _viewEvents.post( OnboardingViewEvents.Failure(it) ) } } + private fun deeplinkUrlIsUnavailable(error: Throwable, trigger: OnboardingAction.HomeServerChange) = error.isHomeserverUnavailable() && + loginConfig != null && + trigger is OnboardingAction.HomeServerChange.SelectHomeServer + private fun OnboardingAction.HomeServerChange.SelectHomeServer.resetToDefaultUrl() = copy(homeServerUrl = defaultHomeserverUrl) private suspend fun onAuthenticationStartedSuccess( diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt index 5228e289bc..8d63fbf547 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt @@ -17,17 +17,13 @@ package im.vector.app.features.onboarding.ftueauth import android.widget.Button -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputLayout -import im.vector.app.R import im.vector.app.core.extensions.hasContentFlow -import im.vector.app.core.extensions.inferNoConnectivity import im.vector.app.features.login.SignMode import im.vector.app.features.onboarding.OnboardingAction import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.failure.isHomeserverUnavailable fun SignMode.toAuthenticateAction(login: String, password: String, initialDeviceName: String): OnboardingAction.AuthenticateAction { return when (this) { diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index e4e687536c..3c394181d1 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -18,6 +18,8 @@ package im.vector.app.features.onboarding import android.net.Uri import com.airbnb.mvrx.test.MvRxTestRule +import im.vector.app.R +import im.vector.app.features.login.LoginConfig import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.SignMode @@ -38,6 +40,8 @@ import im.vector.app.test.fakes.FakeUri import im.vector.app.test.fakes.FakeUriFilenameResolver import im.vector.app.test.fakes.FakeVectorFeatures import im.vector.app.test.fakes.FakeVectorOverrides +import im.vector.app.test.fakes.toTestString +import im.vector.app.test.fixtures.aBuildMeta import im.vector.app.test.fixtures.aHomeServerCapabilities import im.vector.app.test.test import kotlinx.coroutines.test.runTest @@ -242,6 +246,28 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given unavailable deeplink, when selecting homeserver, then emits failure with default homeserver as retry action`() = runTest { + fakeContext.givenHasConnection() + fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG) + fakeStartAuthenticationFlowUseCase.givenHomeserverUnavailable(A_HOMESERVER_CONFIG) + val test = viewModel.test() + + viewModel.handle(OnboardingAction.InitWith(LoginConfig(A_HOMESERVER_URL, null))) + viewModel.handle(OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL)) + + val expectedRetryAction = OnboardingAction.HomeServerChange.SelectHomeServer("${R.string.matrix_org_server_url.toTestString()}/") + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + + ) + .assertEvents(OnboardingViewEvents.DeeplinkAuthenticationFailure(expectedRetryAction)) + .finish() + } + @Test fun `given in the sign up flow, when editing homeserver, then updates selected homeserver state and emits edited event`() = runTest { viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp)) @@ -457,7 +483,8 @@ class OnboardingViewModelTest { fakeRegisterActionHandler.instance, fakeDirectLoginUseCase.instance, fakeStartAuthenticationFlowUseCase.instance, - FakeVectorOverrides() + FakeVectorOverrides(), + aBuildMeta() ).also { viewModel = it initialState = state diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeConnectivityManager.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeConnectivityManager.kt new file mode 100644 index 0000000000..d565105f81 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeConnectivityManager.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import io.mockk.every +import io.mockk.mockk + +class FakeConnectivityManager { + val instance = mockk() + + fun givenNoActiveConnection() { + every { instance.activeNetwork } returns null + } + + fun givenHasActiveConnection() { + val network = mockk() + every { instance.activeNetwork } returns network + + val networkCapabilities = FakeNetworkCapabilities() + networkCapabilities.givenTransports( + NetworkCapabilities.TRANSPORT_CELLULAR, + NetworkCapabilities.TRANSPORT_WIFI, + NetworkCapabilities.TRANSPORT_VPN + ) + every { instance.getNetworkCapabilities(network) } returns networkCapabilities.instance + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index 2a50c34ca3..eb491c9e0c 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -18,6 +18,7 @@ package im.vector.app.test.fakes import android.content.ContentResolver import android.content.Context +import android.net.ConnectivityManager import android.net.Uri import android.os.ParcelFileDescriptor import io.mockk.every @@ -48,4 +49,21 @@ class FakeContext( fun givenMissingSafeOutputStreamFor(uri: Uri) { every { contentResolver.openOutputStream(uri, "wt") } returns null } + + fun givenNoConnection() { + val connectivityManager = FakeConnectivityManager() + connectivityManager.givenNoActiveConnection() + givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance) + } + + private fun givenService(name: String, klass: Class, service: T) { + every { instance.getSystemService(name) } returns service + every { instance.getSystemService(klass) } returns service + } + + fun givenHasConnection() { + val connectivityManager = FakeConnectivityManager() + connectivityManager.givenHasActiveConnection() + givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance) + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeNetworkCapabilities.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeNetworkCapabilities.kt new file mode 100644 index 0000000000..36add7128c --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeNetworkCapabilities.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import android.net.NetworkCapabilities +import io.mockk.every +import io.mockk.mockk + +class FakeNetworkCapabilities { + val instance = mockk() + + fun givenTransports(vararg type: Int) { + every { instance.hasTransport(any()) } answers { + val input = it.invocation.args.first() as Int + type.contains(input) + } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt index 697de6bf25..88ad1a7a6b 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt @@ -18,9 +18,11 @@ package im.vector.app.test.fakes import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult +import im.vector.app.test.fixtures.aHomeserverUnavailableError import io.mockk.coEvery import io.mockk.mockk import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.failure.Failure class FakeStartAuthenticationFlowUseCase { @@ -29,4 +31,8 @@ class FakeStartAuthenticationFlowUseCase { fun givenResult(config: HomeServerConnectionConfig, result: StartAuthenticationResult) { coEvery { instance.execute(config) } returns result } + + fun givenHomeserverUnavailable(config: HomeServerConnectionConfig) { + coEvery { instance.execute(config) } throws aHomeserverUnavailableError() + } } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt index 39c139c208..9ac851ef5e 100644 --- a/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt +++ b/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt @@ -18,8 +18,11 @@ package im.vector.app.test.fixtures import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError +import java.net.UnknownHostException import javax.net.ssl.HttpsURLConnection fun a401ServerError() = Failure.ServerError( MatrixError(MatrixError.M_UNAUTHORIZED, ""), HttpsURLConnection.HTTP_UNAUTHORIZED ) + +fun aHomeserverUnavailableError() = Failure.NetworkConnection(UnknownHostException()) From 73c93958c23fe54023e159f95d89f4c8a277633b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 12 May 2022 15:10:53 +0100 Subject: [PATCH 08/11] adding changelog entry --- changelog.d/6023.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6023.wip diff --git a/changelog.d/6023.wip b/changelog.d/6023.wip new file mode 100644 index 0000000000..aefd62bcd7 --- /dev/null +++ b/changelog.d/6023.wip @@ -0,0 +1 @@ +FTUE - Adds homeserver login/register deeplink support From f6190b125c458ec5f2542ebce332ace6ef87ac21 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 12 May 2022 15:11:12 +0100 Subject: [PATCH 09/11] removing extra line --- .../im/vector/app/features/onboarding/OnboardingViewModelTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 3c394181d1..1abfa7e9a8 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -262,7 +262,6 @@ class OnboardingViewModelTest { initialState, { copy(isLoading = true) }, { copy(isLoading = false) } - ) .assertEvents(OnboardingViewEvents.DeeplinkAuthenticationFailure(expectedRetryAction)) .finish() From 86c9e601291fe120ff8ed791efa4102dae3cd78f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 17 May 2022 10:46:15 +0100 Subject: [PATCH 10/11] formatting --- .../java/im/vector/app/features/onboarding/OnboardingAction.kt | 2 +- .../app/test/fakes/FakeStartAuthenticationFlowUseCase.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index ce387d29fa..bd2ff1a26a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.network.ssl.Fingerprint sealed interface OnboardingAction : VectorViewModelAction { - sealed interface SplashAction: OnboardingAction { + sealed interface SplashAction : OnboardingAction { val onboardingFlow: OnboardingFlow data class OnGetStarted(override val onboardingFlow: OnboardingFlow) : SplashAction diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt index 88ad1a7a6b..bfbef9e565 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt @@ -22,7 +22,6 @@ import im.vector.app.test.fixtures.aHomeserverUnavailableError import io.mockk.coEvery import io.mockk.mockk import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.failure.Failure class FakeStartAuthenticationFlowUseCase { @@ -33,6 +32,6 @@ class FakeStartAuthenticationFlowUseCase { } fun givenHomeserverUnavailable(config: HomeServerConnectionConfig) { - coEvery { instance.execute(config) } throws aHomeserverUnavailableError() + coEvery { instance.execute(config) } throws aHomeserverUnavailableError() } } From 096db6c35d78eb58727fd02b0df9c83373a97e34 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 25 May 2022 17:34:00 +0100 Subject: [PATCH 11/11] giving arugment a proper name --- .../app/features/onboarding/OnboardingViewModel.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 5af4fa138b..bf79873909 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -638,18 +638,18 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun onAuthenticationStartError(it: Throwable, trigger: OnboardingAction.HomeServerChange) { + private fun onAuthenticationStartError(error: Throwable, trigger: OnboardingAction.HomeServerChange) { when { - it.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(buildMeta) -> _viewEvents.post( - OnboardingViewEvents.Failure(it) + error.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(buildMeta) -> _viewEvents.post( + OnboardingViewEvents.Failure(error) ) - deeplinkUrlIsUnavailable(it, trigger) -> _viewEvents.post( + deeplinkUrlIsUnavailable(error, trigger) -> _viewEvents.post( OnboardingViewEvents.DeeplinkAuthenticationFailure( retryAction = (trigger as OnboardingAction.HomeServerChange.SelectHomeServer).resetToDefaultUrl() ) ) - else -> _viewEvents.post( - OnboardingViewEvents.Failure(it) + else -> _viewEvents.post( + OnboardingViewEvents.Failure(error) ) } }