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) {