providing dedicated reset action for resetting invalid deeplink homeserver

- also fixes the usecase screen becoming stuck with an invalid homeserver deeplink
This commit is contained in:
Adam Brown 2022-05-11 18:29:44 +01:00
parent 8c44c9828c
commit 690fda180c
5 changed files with 101 additions and 33 deletions

View file

@ -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

View file

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

View file

@ -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
}

View file

@ -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()

View file

@ -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<FragmentFtueAuthUseCaseBinding>() {
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()
}
}