lifting unavailable homeserver rendering to the activity/ftuevariant
- the viewmodel is now responsible for inferring connectivity errors and providing a retry action
This commit is contained in:
parent
100aa24021
commit
ea7df9b673
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue