mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-29 06:28:45 +03:00
Merge pull request #6036 from vector-im/feature/adm/ftue-deeplinks
FTUE - Homeserver sign in/up deeplinks
This commit is contained in:
commit
7fdf138e5a
18 changed files with 343 additions and 128 deletions
1
changelog.d/6023.wip
Normal file
1
changelog.d/6023.wip
Normal file
|
@ -0,0 +1 @@
|
||||||
|
FTUE - Adds homeserver login/register deeplink support
|
|
@ -33,6 +33,7 @@ import im.vector.app.config.analyticsConfig
|
||||||
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
||||||
import im.vector.app.core.error.DefaultErrorFormatter
|
import im.vector.app.core.error.DefaultErrorFormatter
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
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.Clock
|
||||||
import im.vector.app.core.time.DefaultClock
|
import im.vector.app.core.time.DefaultClock
|
||||||
import im.vector.app.features.analytics.AnalyticsConfig
|
import im.vector.app.features.analytics.AnalyticsConfig
|
||||||
|
@ -185,4 +186,8 @@ object VectorStaticModule {
|
||||||
fun providesAnalyticsConfig(): AnalyticsConfig {
|
fun providesAnalyticsConfig(): AnalyticsConfig {
|
||||||
return analyticsConfig
|
return analyticsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesBuildMeta() = BuildMeta()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,13 @@
|
||||||
|
|
||||||
package im.vector.app.core.extensions
|
package im.vector.app.core.extensions
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.ImageSpan
|
import android.text.style.ImageSpan
|
||||||
|
@ -27,11 +31,13 @@ import androidx.annotation.ColorRes
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.FloatRange
|
import androidx.annotation.FloatRange
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import dagger.hilt.EntryPoints
|
import dagger.hilt.EntryPoints
|
||||||
import im.vector.app.core.datastore.dataStoreProvider
|
import im.vector.app.core.datastore.dataStoreProvider
|
||||||
import im.vector.app.core.di.SingletonEntryPoint
|
import im.vector.app.core.di.SingletonEntryPoint
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@ -77,3 +83,31 @@ val Context.dataStoreProvider: (String) -> DataStore<Preferences> by dataStorePr
|
||||||
fun Context.safeOpenOutputStream(uri: Uri): OutputStream? {
|
fun Context.safeOpenOutputStream(uri: Uri): OutputStream? {
|
||||||
return contentResolver.openOutputStream(uri, "wt")
|
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")
|
||||||
|
@SuppressLint("NewApi") // false positive
|
||||||
|
fun Context.inferNoConnectivity(buildMeta: BuildMeta): Boolean {
|
||||||
|
val connectivityManager = getSystemService<ConnectivityManager>()!!
|
||||||
|
return if (buildMeta.sdkInt > 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -25,8 +25,12 @@ import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
||||||
|
|
||||||
sealed interface OnboardingAction : VectorViewModelAction {
|
sealed interface OnboardingAction : VectorViewModelAction {
|
||||||
data class OnGetStarted(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction
|
sealed interface SplashAction : OnboardingAction {
|
||||||
data class OnIAlreadyHaveAnAccount(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : 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
|
data class UpdateServerType(val serverType: ServerType) : OnboardingAction
|
||||||
|
|
||||||
|
@ -58,7 +62,7 @@ sealed interface OnboardingAction : VectorViewModelAction {
|
||||||
|
|
||||||
// Reset actions
|
// Reset actions
|
||||||
sealed interface ResetAction : OnboardingAction
|
sealed interface ResetAction : OnboardingAction
|
||||||
|
object ResetDeeplinkConfig : ResetAction
|
||||||
object ResetHomeServerType : ResetAction
|
object ResetHomeServerType : ResetAction
|
||||||
object ResetHomeServerUrl : ResetAction
|
object ResetHomeServerUrl : ResetAction
|
||||||
object ResetSignMode : ResetAction
|
object ResetSignMode : ResetAction
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||||
sealed class OnboardingViewEvents : VectorViewEvents {
|
sealed class OnboardingViewEvents : VectorViewEvents {
|
||||||
data class Loading(val message: CharSequence? = null) : OnboardingViewEvents()
|
data class Loading(val message: CharSequence? = null) : OnboardingViewEvents()
|
||||||
data class Failure(val throwable: Throwable) : 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()
|
data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : OnboardingViewEvents()
|
||||||
object OutdatedHomeserver : OnboardingViewEvents()
|
object OutdatedHomeserver : OnboardingViewEvents()
|
||||||
|
|
|
@ -27,9 +27,12 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.extensions.cancelCurrentOnSet
|
import im.vector.app.core.extensions.cancelCurrentOnSet
|
||||||
import im.vector.app.core.extensions.configureAndStart
|
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.extensions.vectorStore
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
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.resources.StringProvider
|
||||||
|
import im.vector.app.core.utils.ensureProtocol
|
||||||
import im.vector.app.core.utils.ensureTrailingSlash
|
import im.vector.app.core.utils.ensureTrailingSlash
|
||||||
import im.vector.app.features.VectorFeatures
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.VectorOverrides
|
import im.vector.app.features.VectorOverrides
|
||||||
|
@ -55,6 +58,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.FlowResult
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||||
|
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -78,7 +82,8 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
private val registrationActionHandler: RegistrationActionHandler,
|
private val registrationActionHandler: RegistrationActionHandler,
|
||||||
private val directLoginUseCase: DirectLoginUseCase,
|
private val directLoginUseCase: DirectLoginUseCase,
|
||||||
private val startAuthenticationFlowUseCase: StartAuthenticationFlowUseCase,
|
private val startAuthenticationFlowUseCase: StartAuthenticationFlowUseCase,
|
||||||
private val vectorOverrides: VectorOverrides
|
private val vectorOverrides: VectorOverrides,
|
||||||
|
private val buildMeta: BuildMeta
|
||||||
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
|
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -132,8 +137,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
override fun handle(action: OnboardingAction) {
|
override fun handle(action: OnboardingAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
|
is OnboardingAction.SplashAction -> handleSplashAction(action)
|
||||||
is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
|
|
||||||
is OnboardingAction.UpdateUseCase -> handleUpdateUseCase(action)
|
is OnboardingAction.UpdateUseCase -> handleUpdateUseCase(action)
|
||||||
OnboardingAction.ResetUseCase -> resetUseCase()
|
OnboardingAction.ResetUseCase -> resetUseCase()
|
||||||
is OnboardingAction.UpdateServerType -> handleUpdateServerType(action)
|
is OnboardingAction.UpdateServerType -> handleUpdateServerType(action)
|
||||||
|
@ -173,26 +177,9 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) {
|
private fun handleSplashAction(action: OnboardingAction.SplashAction) {
|
||||||
if (resetConfig) {
|
setState { copy(onboardingFlow = action.onboardingFlow) }
|
||||||
loginConfig = null
|
continueToPageAfterSplash(action.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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) {
|
private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) {
|
||||||
|
@ -206,10 +193,21 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
OnboardingFlow.SignIn -> if (vectorFeatures.isOnboardingCombinedLoginEnabled()) {
|
OnboardingFlow.SignIn -> when {
|
||||||
handle(OnboardingAction.HomeServerChange.SelectHomeServer(defaultHomeserverUrl))
|
vectorFeatures.isOnboardingCombinedLoginEnabled() -> {
|
||||||
} else _viewEvents.post(OnboardingViewEvents.OpenServerSelection)
|
handle(OnboardingAction.HomeServerChange.SelectHomeServer(deeplinkOrDefaultHomeserverUrl()))
|
||||||
OnboardingFlow.SignInSignUp -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection)
|
}
|
||||||
|
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 +218,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
is OnboardingAction.HomeServerChange.SelectHomeServer -> {
|
is OnboardingAction.HomeServerChange.SelectHomeServer -> {
|
||||||
currentHomeServerConnectionConfig
|
currentHomeServerConnectionConfig
|
||||||
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
||||||
?.let { startAuthenticationFlow(finalLastAction, it) }
|
?.let { startAuthenticationFlow(finalLastAction, it, serverTypeOverride = null) }
|
||||||
}
|
}
|
||||||
is AuthenticateAction.LoginDirect ->
|
is AuthenticateAction.LoginDirect ->
|
||||||
handleDirectLogin(
|
handleDirectLogin(
|
||||||
|
@ -374,6 +372,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
OnboardingAction.ResetDeeplinkConfig -> loginConfig = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,11 +393,13 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
private fun handleUpdateUseCase(action: OnboardingAction.UpdateUseCase) {
|
private fun handleUpdateUseCase(action: OnboardingAction.UpdateUseCase) {
|
||||||
setState { copy(useCase = action.useCase) }
|
setState { copy(useCase = action.useCase) }
|
||||||
when (vectorFeatures.isOnboardingCombinedRegisterEnabled()) {
|
when (vectorFeatures.isOnboardingCombinedRegisterEnabled()) {
|
||||||
true -> handle(OnboardingAction.HomeServerChange.SelectHomeServer(defaultHomeserverUrl))
|
true -> handle(OnboardingAction.HomeServerChange.SelectHomeServer(deeplinkOrDefaultHomeserverUrl()))
|
||||||
false -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection)
|
false -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deeplinkOrDefaultHomeserverUrl() = loginConfig?.homeServerUrl?.ensureProtocol() ?: defaultHomeserverUrl
|
||||||
|
|
||||||
private fun resetUseCase() {
|
private fun resetUseCase() {
|
||||||
setState { copy(useCase = null) }
|
setState { copy(useCase = null) }
|
||||||
}
|
}
|
||||||
|
@ -422,7 +423,6 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun handleInitWith(action: OnboardingAction.InitWith) {
|
private fun handleInitWith(action: OnboardingAction.InitWith) {
|
||||||
loginConfig = action.loginConfig
|
loginConfig = action.loginConfig
|
||||||
|
|
||||||
// If there is a pending email validation continue on this step
|
// If there is a pending email validation continue on this step
|
||||||
try {
|
try {
|
||||||
if (registrationWizard.isRegistrationStarted()) {
|
if (registrationWizard.isRegistrationStarted()) {
|
||||||
|
@ -611,20 +611,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)
|
val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl)
|
||||||
if (homeServerConnectionConfig == null) {
|
if (homeServerConnectionConfig == null) {
|
||||||
// This is invalid
|
// This is invalid
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||||
} else {
|
} else {
|
||||||
startAuthenticationFlow(action, homeServerConnectionConfig)
|
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startAuthenticationFlow(
|
private fun startAuthenticationFlow(
|
||||||
trigger: OnboardingAction?,
|
trigger: OnboardingAction.HomeServerChange,
|
||||||
homeServerConnectionConfig: HomeServerConnectionConfig,
|
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
serverTypeOverride: ServerType? = null
|
serverTypeOverride: ServerType?
|
||||||
) {
|
) {
|
||||||
currentHomeServerConnectionConfig = homeServerConnectionConfig
|
currentHomeServerConnectionConfig = homeServerConnectionConfig
|
||||||
|
|
||||||
|
@ -632,14 +632,36 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
setState { copy(isLoading = true) }
|
setState { copy(isLoading = true) }
|
||||||
runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold(
|
runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold(
|
||||||
onSuccess = { onAuthenticationStartedSuccess(trigger, homeServerConnectionConfig, it, serverTypeOverride) },
|
onSuccess = { onAuthenticationStartedSuccess(trigger, homeServerConnectionConfig, it, serverTypeOverride) },
|
||||||
onFailure = { _viewEvents.post(OnboardingViewEvents.Failure(it)) }
|
onFailure = { onAuthenticationStartError(it, trigger) }
|
||||||
)
|
)
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onAuthenticationStartError(error: Throwable, trigger: OnboardingAction.HomeServerChange) {
|
||||||
|
when {
|
||||||
|
error.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(buildMeta) -> _viewEvents.post(
|
||||||
|
OnboardingViewEvents.Failure(error)
|
||||||
|
)
|
||||||
|
deeplinkUrlIsUnavailable(error, trigger) -> _viewEvents.post(
|
||||||
|
OnboardingViewEvents.DeeplinkAuthenticationFailure(
|
||||||
|
retryAction = (trigger as OnboardingAction.HomeServerChange.SelectHomeServer).resetToDefaultUrl()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else -> _viewEvents.post(
|
||||||
|
OnboardingViewEvents.Failure(error)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
private suspend fun onAuthenticationStartedSuccess(
|
||||||
trigger: OnboardingAction?,
|
trigger: OnboardingAction.HomeServerChange,
|
||||||
config: HomeServerConnectionConfig,
|
config: HomeServerConnectionConfig,
|
||||||
authResult: StartAuthenticationResult,
|
authResult: StartAuthenticationResult,
|
||||||
serverTypeOverride: ServerType?
|
serverTypeOverride: ServerType?
|
||||||
|
@ -650,47 +672,51 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
when (trigger) {
|
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 -> {
|
is OnboardingAction.HomeServerChange.SelectHomeServer -> {
|
||||||
updateServerSelection(config, serverTypeOverride, authResult)
|
onHomeServerSelected(config, serverTypeOverride, authResult)
|
||||||
if (authResult.selectedHomeserver.preferredLoginMode.supportsSignModeScreen()) {
|
}
|
||||||
when (awaitState().onboardingFlow) {
|
is OnboardingAction.HomeServerChange.EditHomeServer -> {
|
||||||
OnboardingFlow.SignIn -> {
|
onHomeServerEdited(config, serverTypeOverride, authResult)
|
||||||
updateSignMode(SignMode.SignIn)
|
}
|
||||||
when (vectorFeatures.isOnboardingCombinedLoginEnabled()) {
|
}
|
||||||
true -> _viewEvents.post(OnboardingViewEvents.OpenCombinedLogin)
|
}
|
||||||
false -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn))
|
|
||||||
}
|
private suspend fun onHomeServerSelected(config: HomeServerConnectionConfig, serverTypeOverride: ServerType?, authResult: StartAuthenticationResult) {
|
||||||
}
|
updateServerSelection(config, serverTypeOverride, authResult)
|
||||||
OnboardingFlow.SignUp -> {
|
if (authResult.selectedHomeserver.preferredLoginMode.supportsSignModeScreen()) {
|
||||||
updateSignMode(SignMode.SignUp)
|
when (awaitState().onboardingFlow) {
|
||||||
internalRegisterAction(RegisterAction.StartRegistration, ::emitFlowResultViewEvent)
|
OnboardingFlow.SignIn -> {
|
||||||
}
|
updateSignMode(SignMode.SignIn)
|
||||||
OnboardingFlow.SignInSignUp,
|
when (vectorFeatures.isOnboardingCombinedLoginEnabled()) {
|
||||||
null -> {
|
true -> _viewEvents.post(OnboardingViewEvents.OpenCombinedLogin)
|
||||||
_viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved)
|
false -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
OnboardingFlow.SignUp -> {
|
||||||
|
updateSignMode(SignMode.SignUp)
|
||||||
|
internalRegisterAction(RegisterAction.StartRegistration, ::emitFlowResultViewEvent)
|
||||||
|
}
|
||||||
|
OnboardingFlow.SignInSignUp,
|
||||||
|
null -> {
|
||||||
_viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved)
|
_viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
} 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)
|
updateServerSelection(config, serverTypeOverride, authResult)
|
||||||
_viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved)
|
_viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
|
||||||
}
|
}
|
||||||
|
OnboardingFlow.SignIn -> {
|
||||||
|
updateServerSelection(config, serverTypeOverride, authResult)
|
||||||
|
_viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException("developer error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,6 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
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 com.google.android.material.tabs.TabLayoutMediator
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
@ -41,8 +39,6 @@ import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val CAROUSEL_ROTATION_DELAY_MS = 5000L
|
private const val CAROUSEL_ROTATION_DELAY_MS = 5000L
|
||||||
|
@ -128,33 +124,14 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
|
||||||
|
|
||||||
private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) {
|
private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) {
|
||||||
val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
|
val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
|
||||||
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
|
viewModel.handle(OnboardingAction.SplashAction.OnGetStarted(onboardingFlow = getStartedFlow))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun alreadyHaveAnAccount() {
|
private fun alreadyHaveAnAccount() {
|
||||||
viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(resetLoginConfig = false, onboardingFlow = OnboardingFlow.SignIn))
|
viewModel.handle(OnboardingAction.SplashAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.SignIn))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
// Nothing to do
|
// 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.OnGetStarted(resetLoginConfig = true, flow))
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.action_cancel, null)
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
super.onError(throwable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
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.BuildConfig
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.databinding.FragmentFtueAuthSplashBinding
|
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.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.OnboardingFlow
|
import im.vector.app.features.onboarding.OnboardingFlow
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,33 +71,14 @@ class FtueAuthSplashFragment @Inject constructor(
|
||||||
|
|
||||||
private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) {
|
private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) {
|
||||||
val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
|
val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
|
||||||
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
|
viewModel.handle(OnboardingAction.SplashAction.OnGetStarted(onboardingFlow = getStartedFlow))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun alreadyHaveAnAccount() {
|
private fun alreadyHaveAnAccount() {
|
||||||
viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(resetLoginConfig = false, onboardingFlow = OnboardingFlow.SignIn))
|
viewModel.handle(OnboardingAction.SplashAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.SignIn))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
// Nothing to do
|
// 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.OnGetStarted(resetLoginConfig = true, flow))
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.action_cancel, null)
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
super.onError(throwable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ private const val DARK_MODE_ICON_BACKGROUND_ALPHA = 0.30f
|
||||||
private const val LIGHT_MODE_ICON_BACKGROUND_ALPHA = 0.15f
|
private const val LIGHT_MODE_ICON_BACKGROUND_ALPHA = 0.15f
|
||||||
|
|
||||||
class FtueAuthUseCaseFragment @Inject constructor(
|
class FtueAuthUseCaseFragment @Inject constructor(
|
||||||
private val themeProvider: ThemeProvider
|
private val themeProvider: ThemeProvider,
|
||||||
) : AbstractFtueAuthFragment<FragmentFtueAuthUseCaseBinding>() {
|
) : AbstractFtueAuthFragment<FragmentFtueAuthUseCaseBinding>() {
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthUseCaseBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthUseCaseBinding {
|
||||||
|
@ -104,7 +104,7 @@ class FtueAuthUseCaseFragment @Inject constructor(
|
||||||
private fun createIcon(@ColorRes tint: Int, icon: Int, isLightMode: Boolean): Drawable {
|
private fun createIcon(@ColorRes tint: Int, icon: Int, isLightMode: Boolean): Drawable {
|
||||||
val context = requireContext()
|
val context = requireContext()
|
||||||
val alpha = when (isLightMode) {
|
val alpha = when (isLightMode) {
|
||||||
true -> LIGHT_MODE_ICON_BACKGROUND_ALPHA
|
true -> LIGHT_MODE_ICON_BACKGROUND_ALPHA
|
||||||
false -> DARK_MODE_ICON_BACKGROUND_ALPHA
|
false -> DARK_MODE_ICON_BACKGROUND_ALPHA
|
||||||
}
|
}
|
||||||
val iconBackground = context.getResTintedDrawable(R.drawable.bg_feature_icon, tint, alpha = alpha)
|
val iconBackground = context.getResTintedDrawable(R.drawable.bg_feature_icon, tint, alpha = alpha)
|
||||||
|
|
|
@ -227,11 +227,28 @@ class FtueAuthVariant(
|
||||||
option = commonOption
|
option = commonOption
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack()
|
OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack()
|
||||||
OnboardingViewEvents.OpenCombinedLogin -> onStartCombinedLogin()
|
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() {
|
private fun onStartCombinedLogin() {
|
||||||
addRegistrationStageFragmentToBackstack(FtueAuthCombinedLoginFragment::class.java)
|
addRegistrationStageFragmentToBackstack(FtueAuthCombinedLoginFragment::class.java)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ package im.vector.app.features.onboarding
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.airbnb.mvrx.test.MvRxTestRule
|
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.LoginMode
|
||||||
import im.vector.app.features.login.ReAuthHelper
|
import im.vector.app.features.login.ReAuthHelper
|
||||||
import im.vector.app.features.login.SignMode
|
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.FakeUriFilenameResolver
|
||||||
import im.vector.app.test.fakes.FakeVectorFeatures
|
import im.vector.app.test.fakes.FakeVectorFeatures
|
||||||
import im.vector.app.test.fakes.FakeVectorOverrides
|
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.fixtures.aHomeServerCapabilities
|
||||||
import im.vector.app.test.test
|
import im.vector.app.test.test
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
@ -242,6 +246,27 @@ class OnboardingViewModelTest {
|
||||||
.finish()
|
.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
|
@Test
|
||||||
fun `given in the sign up flow, when editing homeserver, then updates selected homeserver state and emits edited event`() = runTest {
|
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))
|
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
||||||
|
@ -457,7 +482,8 @@ class OnboardingViewModelTest {
|
||||||
fakeRegisterActionHandler.instance,
|
fakeRegisterActionHandler.instance,
|
||||||
fakeDirectLoginUseCase.instance,
|
fakeDirectLoginUseCase.instance,
|
||||||
fakeStartAuthenticationFlowUseCase.instance,
|
fakeStartAuthenticationFlowUseCase.instance,
|
||||||
FakeVectorOverrides()
|
FakeVectorOverrides(),
|
||||||
|
aBuildMeta()
|
||||||
).also {
|
).also {
|
||||||
viewModel = it
|
viewModel = it
|
||||||
initialState = state
|
initialState = state
|
||||||
|
|
|
@ -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<ConnectivityManager>()
|
||||||
|
|
||||||
|
fun givenNoActiveConnection() {
|
||||||
|
every { instance.activeNetwork } returns null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenHasActiveConnection() {
|
||||||
|
val network = mockk<Network>()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package im.vector.app.test.fakes
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
@ -48,4 +49,21 @@ class FakeContext(
|
||||||
fun givenMissingSafeOutputStreamFor(uri: Uri) {
|
fun givenMissingSafeOutputStreamFor(uri: Uri) {
|
||||||
every { contentResolver.openOutputStream(uri, "wt") } returns null
|
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 <T> givenService(name: String, klass: Class<T>, 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<NetworkCapabilities>()
|
||||||
|
|
||||||
|
fun givenTransports(vararg type: Int) {
|
||||||
|
every { instance.hasTransport(any()) } answers {
|
||||||
|
val input = it.invocation.args.first() as Int
|
||||||
|
type.contains(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package im.vector.app.test.fakes
|
||||||
|
|
||||||
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase
|
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase
|
||||||
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
|
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
|
||||||
|
import im.vector.app.test.fixtures.aHomeserverUnavailableError
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||||
|
@ -29,4 +30,8 @@ class FakeStartAuthenticationFlowUseCase {
|
||||||
fun givenResult(config: HomeServerConnectionConfig, result: StartAuthenticationResult) {
|
fun givenResult(config: HomeServerConnectionConfig, result: StartAuthenticationResult) {
|
||||||
coEvery { instance.execute(config) } returns result
|
coEvery { instance.execute(config) } returns result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenHomeserverUnavailable(config: HomeServerConnectionConfig) {
|
||||||
|
coEvery { instance.execute(config) } throws aHomeserverUnavailableError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
22
vector/src/test/java/im/vector/app/test/fixtures/BuildMetaFixture.kt
vendored
Normal file
22
vector/src/test/java/im/vector/app/test/fixtures/BuildMetaFixture.kt
vendored
Normal file
|
@ -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)
|
|
@ -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.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
|
import java.net.UnknownHostException
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
fun a401ServerError() = Failure.ServerError(
|
fun a401ServerError() = Failure.ServerError(
|
||||||
MatrixError(MatrixError.M_UNAUTHORIZED, ""), HttpsURLConnection.HTTP_UNAUTHORIZED
|
MatrixError(MatrixError.M_UNAUTHORIZED, ""), HttpsURLConnection.HTTP_UNAUTHORIZED
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun aHomeserverUnavailableError() = Failure.NetworkConnection(UnknownHostException())
|
||||||
|
|
Loading…
Reference in a new issue