mirror of
https://github.com/bitwarden/android.git
synced 2026-06-10 00:28:29 -05:00
Compare commits
1 Commits
overlay-na
...
PM-38808/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f24711ac7 |
@@ -18,7 +18,6 @@
|
||||
<!-- CRL Distribution Servers -->
|
||||
<domain includeSubdomains="true">c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">c.pki.goog</domain>
|
||||
<domain includeSubdomains="true">crls.certainly.com</domain>
|
||||
|
||||
<!-- OCSP Responder Servers -->
|
||||
<domain includeSubdomains="true">o.pki.goog</domain>
|
||||
|
||||
@@ -36,11 +36,17 @@ import com.x8bit.bitwarden.data.platform.manager.util.ObserveScreenDataEffect
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberBitwardenNavController
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
|
||||
import com.x8bit.bitwarden.ui.platform.feature.accessibilitydisclosure.accessibilityDisclosureDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.accessibilitydisclosure.navigateToAccessibilityDisclosure
|
||||
import com.x8bit.bitwarden.ui.platform.feature.cookieacquisition.cookieAcquisitionDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.cookieacquisition.navigateToCookieAcquisition
|
||||
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.debugMenuDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
|
||||
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
|
||||
import com.x8bit.bitwarden.ui.platform.feature.overlaynav.OverlayNavRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.overlaynav.overlayNavDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.localnetworkaccess.localNetworkAccessDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.localnetworkaccess.navigateToLocalNetworkAccess
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavigationRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.x8bit.bitwarden.ui.platform.model.AuthTabLaunchers
|
||||
import com.x8bit.bitwarden.ui.platform.util.appLanguage
|
||||
@@ -106,6 +112,7 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
intent = intent.validate()
|
||||
var shouldShowSplashScreen = true
|
||||
@@ -141,17 +148,31 @@ class MainActivity : AppCompatActivity() {
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = OverlayNavRoute,
|
||||
startDestination = RootNavigationRoute,
|
||||
modifier = Modifier
|
||||
.background(color = BitwardenTheme.colorScheme.background.primary),
|
||||
) {
|
||||
// The OverlayNav and Debug destinations are the only UIs that can be
|
||||
// displayed here, everything else should be inside the OverlayNav.
|
||||
overlayNavDestination { shouldShowSplashScreen = false }
|
||||
// Root navigation, debug menu, and cookie acquisition exist at
|
||||
// this top level. They can appear on top of the rest of the app
|
||||
// without interacting with the state-based navigation used by
|
||||
// RootNavScreen.
|
||||
rootNavDestination { shouldShowSplashScreen = false }
|
||||
debugMenuDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
)
|
||||
cookieAcquisitionDestination(
|
||||
onDismiss = { navController.popBackStack() },
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
)
|
||||
localNetworkAccessDestination(
|
||||
onDismiss = { navController.popBackStack() },
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
)
|
||||
accessibilityDisclosureDestination(
|
||||
onDismiss = { navController.popBackStack() },
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,7 +207,7 @@ class MainActivity : AppCompatActivity() {
|
||||
locales.get(0)?.appLanguage
|
||||
}
|
||||
} else {
|
||||
// For older versions, use whatever language is available from the repository.
|
||||
// For older versions, use what ever language is available from the repository.
|
||||
settingsRepository.appLanguage
|
||||
}
|
||||
|
||||
@@ -235,6 +256,15 @@ class MainActivity : AppCompatActivity() {
|
||||
is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
|
||||
MainEvent.Recreate -> handleRecreate()
|
||||
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
|
||||
MainEvent.NavigateToCookieAcquisition -> navController.navigateToCookieAcquisition()
|
||||
MainEvent.NavigateToLocalNetworkAccess -> {
|
||||
navController.navigateToLocalNetworkAccess()
|
||||
}
|
||||
|
||||
MainEvent.NavigateToAccessibilityDisclosure -> {
|
||||
navController.navigateToAccessibilityDisclosure()
|
||||
}
|
||||
|
||||
is MainEvent.UpdateAppLocale -> {
|
||||
AppCompatDelegate.setApplicationLocales(
|
||||
LocaleListCompat.forLanguageTags(event.localeName),
|
||||
|
||||
@@ -31,11 +31,13 @@ import com.x8bit.bitwarden.data.billing.util.getPremiumCheckoutCallbackResult
|
||||
import com.x8bit.bitwarden.data.credentials.manager.CredentialProviderRequestManager
|
||||
import com.x8bit.bitwarden.data.credentials.manager.model.CredentialProviderRequest
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppResumeManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.CookieAcquisitionRequestManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.garbage.GarbageCollectionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkPermissionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
|
||||
@@ -55,6 +57,7 @@ import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -78,6 +81,8 @@ private const val ANIMATION_DEBOUNCE_DELAY_MS = 500L
|
||||
class MainViewModel @Inject constructor(
|
||||
accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||
autofillSelectionManager: AutofillSelectionManager,
|
||||
cookieAcquisitionRequestManager: CookieAcquisitionRequestManager,
|
||||
networkPermissionManager: NetworkPermissionManager,
|
||||
private val addTotpItemFromAuthenticatorManager: AddTotpItemFromAuthenticatorManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val garbageCollectionManager: GarbageCollectionManager,
|
||||
@@ -147,6 +152,12 @@ class MainViewModel @Inject constructor(
|
||||
.onEach(::trySendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
settingsRepository
|
||||
.hasShownAccessibilityDisclaimerFlow
|
||||
.map { MainAction.Internal.HasShownAccessibilityDisclaimerUpdate(it) }
|
||||
.onEach(::trySendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
merge(
|
||||
authRepository
|
||||
.userStateFlow
|
||||
@@ -166,6 +177,20 @@ class MainViewModel @Inject constructor(
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
networkPermissionManager
|
||||
.isLocalNetworkAccessRequiredStateFlow
|
||||
.filter { it }
|
||||
.map { MainAction.Internal.LocalNetworkAccessRequired }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
cookieAcquisitionRequestManager
|
||||
.cookieAcquisitionRequestFlow
|
||||
.filterNotNull()
|
||||
.map { MainAction.Internal.CookieAcquisitionReady }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
// On app launch, mark all active users as having previously logged in.
|
||||
// This covers any users who are active prior to this value being recorded.
|
||||
viewModelScope.launch {
|
||||
@@ -214,7 +239,20 @@ class MainViewModel @Inject constructor(
|
||||
is MainAction.Internal.ScreenCaptureUpdate -> handleScreenCaptureUpdate(action)
|
||||
is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action)
|
||||
is MainAction.Internal.DynamicColorsUpdate -> handleDynamicColorsUpdate(action)
|
||||
is MainAction.Internal.CookieAcquisitionReady -> handleCookieAcquisitionReady()
|
||||
is MainAction.Internal.LocalNetworkAccessRequired -> handleLocalNetworkAccessRequired()
|
||||
is MainAction.Internal.ResizeHasBeenRequested -> handleResizeHasBeenRequested()
|
||||
is MainAction.Internal.HasShownAccessibilityDisclaimerUpdate -> {
|
||||
handleHasShownAccessibilityDisclaimerUpdate(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleHasShownAccessibilityDisclaimerUpdate(
|
||||
action: MainAction.Internal.HasShownAccessibilityDisclaimerUpdate,
|
||||
) {
|
||||
if (!action.hasBeenShown) {
|
||||
sendEvent(MainEvent.NavigateToAccessibilityDisclosure)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +333,14 @@ class MainViewModel @Inject constructor(
|
||||
mutableStateFlow.update { it.copy(isDynamicColorsEnabled = action.isDynamicColorsEnabled) }
|
||||
}
|
||||
|
||||
private fun handleCookieAcquisitionReady() {
|
||||
sendEvent(MainEvent.NavigateToCookieAcquisition)
|
||||
}
|
||||
|
||||
private fun handleLocalNetworkAccessRequired() {
|
||||
sendEvent(MainEvent.NavigateToLocalNetworkAccess)
|
||||
}
|
||||
|
||||
private fun handleResizeHasBeenRequested() {
|
||||
mutableStateFlow.update { it.copy(hasResizeBeenRequested = true) }
|
||||
}
|
||||
@@ -649,10 +695,26 @@ sealed class MainAction {
|
||||
val isDynamicColorsEnabled: Boolean,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the cookie acquisition conditions are met and navigation
|
||||
* should proceed.
|
||||
*/
|
||||
data object CookieAcquisitionReady : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the local network access is required.
|
||||
*/
|
||||
data object LocalNetworkAccessRequired : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that resize has been requested on the Activity
|
||||
*/
|
||||
data object ResizeHasBeenRequested : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the accessibility disclaimer has been displayed.
|
||||
*/
|
||||
data class HasShownAccessibilityDisclaimerUpdate(val hasBeenShown: Boolean) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,6 +744,21 @@ sealed class MainEvent {
|
||||
*/
|
||||
data object NavigateToDebugMenu : MainEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the cookie acquisition screen.
|
||||
*/
|
||||
data object NavigateToCookieAcquisition : MainEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the local network access screen.
|
||||
*/
|
||||
data object NavigateToLocalNetworkAccess : MainEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the accessibility disclosure screen.
|
||||
*/
|
||||
data object NavigateToAccessibilityDisclosure : MainEvent()
|
||||
|
||||
/**
|
||||
* Indicates that the app language has been updated.
|
||||
*/
|
||||
|
||||
@@ -829,7 +829,7 @@ fun Cipher.toFailureCipherListView(): CipherListView =
|
||||
folderId = folderId,
|
||||
collectionIds = collectionIds,
|
||||
key = key,
|
||||
name = name,
|
||||
name = name.orEmpty(),
|
||||
subtitle = "",
|
||||
type = when (type) {
|
||||
CipherType.LOGIN -> CipherListViewType.Login(
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.x8bit.bitwarden.ui.platform.feature.debugmenu
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ fun NavGraphBuilder.debugMenuDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onSplashScreenRemoved: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions<DebugRoute> {
|
||||
composableWithPushTransitions<DebugRoute> {
|
||||
DebugMenuScreen(onNavigateBack = onNavigateBack)
|
||||
// If we are displaying the debug screen, then we can just hide the splash screen.
|
||||
onSplashScreenRemoved()
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.overlaynav
|
||||
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The type-safe route for the overlay navigation screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object OverlayNavRoute
|
||||
|
||||
/**
|
||||
* Add the overlay navigation screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.overlayNavDestination(
|
||||
onSplashScreenRemoved: () -> Unit,
|
||||
) {
|
||||
composable<OverlayNavRoute> {
|
||||
OverlayNavScreen(onSplashScreenRemoved = onSplashScreenRemoved)
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.overlaynav
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberBitwardenNavController
|
||||
import com.x8bit.bitwarden.ui.platform.feature.accessibilitydisclosure.accessibilityDisclosureDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.accessibilitydisclosure.navigateToAccessibilityDisclosure
|
||||
import com.x8bit.bitwarden.ui.platform.feature.cookieacquisition.cookieAcquisitionDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.cookieacquisition.navigateToCookieAcquisition
|
||||
import com.x8bit.bitwarden.ui.platform.feature.localnetworkaccess.localNetworkAccessDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.localnetworkaccess.navigateToLocalNetworkAccess
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavigationRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination
|
||||
|
||||
/**
|
||||
* Controls the overlay [NavHost] for the app including the [rootNavDestination] and any screen
|
||||
* that can appear on top of it without affecting its state.
|
||||
*/
|
||||
@Composable
|
||||
fun OverlayNavScreen(
|
||||
viewModel: OverlayNavViewModel = hiltViewModel(),
|
||||
navController: NavHostController = rememberBitwardenNavController(name = "OverlayNavScreen"),
|
||||
onSplashScreenRemoved: () -> Unit,
|
||||
) {
|
||||
OverlayNavEventsEffect(
|
||||
viewModel = viewModel,
|
||||
navController = navController,
|
||||
)
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = RootNavigationRoute,
|
||||
) {
|
||||
// This is the overlay level of navigation that sits above the root nav. These screens
|
||||
// can appear on top of the rest of the app without interacting with the state-based
|
||||
// navigation used by RootNavScreen (which also exists here).
|
||||
rootNavDestination(onSplashScreenRemoved = onSplashScreenRemoved)
|
||||
cookieAcquisitionDestination(
|
||||
onDismiss = { navController.popBackStack() },
|
||||
onSplashScreenRemoved = onSplashScreenRemoved,
|
||||
)
|
||||
localNetworkAccessDestination(
|
||||
onDismiss = { navController.popBackStack() },
|
||||
onSplashScreenRemoved = onSplashScreenRemoved,
|
||||
)
|
||||
accessibilityDisclosureDestination(
|
||||
onDismiss = { navController.popBackStack() },
|
||||
onSplashScreenRemoved = onSplashScreenRemoved,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OverlayNavEventsEffect(
|
||||
viewModel: OverlayNavViewModel,
|
||||
navController: NavController,
|
||||
) {
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
OverlayNavEvent.NavigateToCookieAcquisition -> {
|
||||
navController.navigateToCookieAcquisition()
|
||||
}
|
||||
|
||||
OverlayNavEvent.NavigateToLocalNetworkAccess -> {
|
||||
navController.navigateToLocalNetworkAccess()
|
||||
}
|
||||
|
||||
OverlayNavEvent.NavigateToAccessibilityDisclosure -> {
|
||||
navController.navigateToAccessibilityDisclosure()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.overlaynav
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.platform.base.DeferredBackgroundEvent
|
||||
import com.x8bit.bitwarden.data.platform.manager.CookieAcquisitionRequestManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkPermissionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Manages the overlay navigation, hosting the root-navigation and any screen that can overlay it.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class OverlayNavViewModel @Inject constructor(
|
||||
cookieAcquisitionRequestManager: CookieAcquisitionRequestManager,
|
||||
networkPermissionManager: NetworkPermissionManager,
|
||||
settingsRepository: SettingsRepository,
|
||||
) : BaseViewModel<Unit, OverlayNavEvent, OverlayNavAction>(initialState = Unit) {
|
||||
init {
|
||||
settingsRepository
|
||||
.hasShownAccessibilityDisclaimerFlow
|
||||
.filterNot { it }
|
||||
.map { OverlayNavAction.Internal.AccessibilityDisclosureRequired }
|
||||
.onEach(::trySendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
networkPermissionManager
|
||||
.isLocalNetworkAccessRequiredStateFlow
|
||||
.filter { it }
|
||||
.map { OverlayNavAction.Internal.LocalNetworkAccessRequired }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
cookieAcquisitionRequestManager
|
||||
.cookieAcquisitionRequestFlow
|
||||
.filterNotNull()
|
||||
.map { OverlayNavAction.Internal.CookieAcquisitionReady }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: OverlayNavAction) {
|
||||
when (action) {
|
||||
is OverlayNavAction.Internal -> handleInternal(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInternal(action: OverlayNavAction.Internal) {
|
||||
when (action) {
|
||||
OverlayNavAction.Internal.AccessibilityDisclosureRequired -> {
|
||||
handleAccessibilityDisclosureRequired()
|
||||
}
|
||||
|
||||
OverlayNavAction.Internal.CookieAcquisitionReady -> handleCookieAcquisitionReady()
|
||||
OverlayNavAction.Internal.LocalNetworkAccessRequired -> {
|
||||
handleLocalNetworkAccessRequired()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAccessibilityDisclosureRequired() {
|
||||
sendEvent(OverlayNavEvent.NavigateToAccessibilityDisclosure)
|
||||
}
|
||||
|
||||
private fun handleCookieAcquisitionReady() {
|
||||
sendEvent(OverlayNavEvent.NavigateToCookieAcquisition)
|
||||
}
|
||||
|
||||
private fun handleLocalNetworkAccessRequired() {
|
||||
sendEvent(OverlayNavEvent.NavigateToLocalNetworkAccess)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models events for the overlay navigation screen.
|
||||
*/
|
||||
sealed class OverlayNavEvent {
|
||||
/**
|
||||
* Navigate to the cookie acquisition screen.
|
||||
*/
|
||||
data object NavigateToCookieAcquisition : OverlayNavEvent(), DeferredBackgroundEvent
|
||||
|
||||
/**
|
||||
* Navigate to the local network access screen.
|
||||
*/
|
||||
data object NavigateToLocalNetworkAccess : OverlayNavEvent(), DeferredBackgroundEvent
|
||||
|
||||
/**
|
||||
* Navigate to the accessibility disclosure screen.
|
||||
*/
|
||||
data object NavigateToAccessibilityDisclosure : OverlayNavEvent(), DeferredBackgroundEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions for the overlay navigation screen.
|
||||
*/
|
||||
sealed class OverlayNavAction {
|
||||
/**
|
||||
* Internal ViewModel actions.
|
||||
*/
|
||||
sealed class Internal : OverlayNavAction() {
|
||||
|
||||
/**
|
||||
* Indicates that the cookie acquisition conditions are met and navigation
|
||||
* should proceed.
|
||||
*/
|
||||
data object CookieAcquisitionReady : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the local network access is required.
|
||||
*/
|
||||
data object LocalNetworkAccessRequired : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the accessibility disclosure needs to be displayed.
|
||||
*/
|
||||
data object AccessibilityDisclosureRequired : Internal()
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@
|
||||
<!-- CRL Distribution Servers -->
|
||||
<domain includeSubdomains="true">c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">c.pki.goog</domain>
|
||||
<domain includeSubdomains="true">crls.certainly.com</domain>
|
||||
|
||||
<!-- OCSP Responder Servers -->
|
||||
<domain includeSubdomains="true">o.pki.goog</domain>
|
||||
|
||||
@@ -55,14 +55,17 @@ import com.x8bit.bitwarden.data.credentials.model.Fido2CredentialAssertionReques
|
||||
import com.x8bit.bitwarden.data.credentials.model.GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.credentials.model.ProviderGetPasswordCredentialRequest
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppResumeManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.CookieAcquisitionRequestManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.garbage.GarbageCollectionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CookieAcquisitionRequest
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkPermissionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
|
||||
@@ -111,6 +114,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
private val mutableAppLanguageFlow = MutableStateFlow(AppLanguage.DEFAULT)
|
||||
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
||||
private val mutableIsDynamicColorsEnabledFlow = MutableStateFlow(false)
|
||||
private val mutableHasShownAccessibilityDisclaimerFlow = MutableStateFlow(true)
|
||||
private val settingsRepository = mockk<SettingsRepository> {
|
||||
every { appTheme } returns AppTheme.DEFAULT
|
||||
every { appThemeStateFlow } returns mutableAppThemeFlow
|
||||
@@ -121,6 +125,10 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
every { appLanguage = any() } just runs
|
||||
every { isDynamicColorsEnabled } returns false
|
||||
every { isDynamicColorsEnabledFlow } returns mutableIsDynamicColorsEnabledFlow
|
||||
every {
|
||||
hasShownAccessibilityDisclaimerFlow
|
||||
} returns mutableHasShownAccessibilityDisclaimerFlow
|
||||
every { accessibilityDisclaimerHasBeenShown() } just runs
|
||||
}
|
||||
private val authRepository = mockk<AuthRepository> {
|
||||
every { activeUserId } returns DEFAULT_USER_STATE.activeUserId
|
||||
@@ -175,6 +183,17 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
every { show(message = any(), duration = any()) } just runs
|
||||
every { show(messageId = any(), duration = any()) } just runs
|
||||
}
|
||||
private val mutableCookieAcquisitionRequestFlow =
|
||||
MutableStateFlow<CookieAcquisitionRequest?>(null)
|
||||
private val cookieAcquisitionRequestManager: CookieAcquisitionRequestManager = mockk {
|
||||
every { cookieAcquisitionRequestFlow } returns mutableCookieAcquisitionRequestFlow
|
||||
}
|
||||
private val mutableIsLocalNetworkAccessRequiredStateFlow = MutableStateFlow(false)
|
||||
private val networkPermissionManager: NetworkPermissionManager = mockk {
|
||||
every {
|
||||
isLocalNetworkAccessRequiredStateFlow
|
||||
} returns mutableIsLocalNetworkAccessRequiredStateFlow
|
||||
}
|
||||
private val credentialProviderRequestManager: CredentialProviderRequestManager = mockk {
|
||||
every { getPendingCredentialRequest() } returns null
|
||||
}
|
||||
@@ -1300,6 +1319,53 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `cookie acquisition should emit NavigateToCookieAcquisition when vault unlocked with matching hostname`() =
|
||||
runTest {
|
||||
mutableCookieAcquisitionRequestFlow.value = CookieAcquisitionRequest(
|
||||
hostname = DEFAULT_US_WEB_VAULT_URL,
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
// Skip init events (appLanguage + appTheme)
|
||||
skipItems(2)
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE
|
||||
assertEquals(
|
||||
MainEvent.NavigateToCookieAcquisition,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `local network access required should emit NavigateToLocalNetworkAccess when stateflow emits`() =
|
||||
runTest {
|
||||
mutableIsLocalNetworkAccessRequiredStateFlow.value = true
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
// Skip init events (appLanguage + appTheme)
|
||||
skipItems(2)
|
||||
assertEquals(MainEvent.NavigateToLocalNetworkAccess, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cookie acquisition should not emit event when conditions are false`() =
|
||||
runTest {
|
||||
mutableCookieAcquisitionRequestFlow.value = null
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
// Skip init events (appLanguage + appTheme)
|
||||
skipItems(2)
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on handleResizeHasBeenRequested should set hasResizeBeenRequested as true`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
@@ -1324,12 +1390,31 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on HasShownAccessibilityDisclaimerUpdate with false should show the accessibility disclosure`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
// We skip the first 2 events because they are the default appTheme and appLanguage
|
||||
skipItems(2)
|
||||
|
||||
mutableHasShownAccessibilityDisclaimerFlow.value = false
|
||||
assertEquals(MainEvent.NavigateToAccessibilityDisclosure, awaitItem())
|
||||
|
||||
mutableHasShownAccessibilityDisclaimerFlow.value = true
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
initialSpecialCircumstance: SpecialCircumstance? = null,
|
||||
): MainViewModel = MainViewModel(
|
||||
accessibilitySelectionManager = accessibilitySelectionManager,
|
||||
addTotpItemFromAuthenticatorManager = addTotpItemAuthenticatorManager,
|
||||
autofillSelectionManager = autofillSelectionManager,
|
||||
cookieAcquisitionRequestManager = cookieAcquisitionRequestManager,
|
||||
networkPermissionManager = networkPermissionManager,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
garbageCollectionManager = garbageCollectionManager,
|
||||
credentialProviderRequestManager = credentialProviderRequestManager,
|
||||
@@ -1360,6 +1445,7 @@ private val DEFAULT_FIRST_TIME_STATE = FirstTimeState(
|
||||
|
||||
private const val SPECIAL_CIRCUMSTANCE_KEY: String = "special-circumstance"
|
||||
private const val ACTIVE_USER_ID: String = "activeUserId"
|
||||
private const val DEFAULT_US_WEB_VAULT_URL: String = "https://vault.bitwarden.com"
|
||||
private val DEFAULT_ACCOUNT = UserState.Account(
|
||||
userId = ACTIVE_USER_ID,
|
||||
name = "Active User",
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.overlaynav
|
||||
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.ui.platform.base.createMockNavHostController
|
||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.feature.accessibilitydisclosure.AccessibilityDisclosureRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.cookieacquisition.CookieAcquisitionRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.localnetworkaccess.LocalNetworkAccessRoute
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class OverlayNavScreenTest : BitwardenComposeTest() {
|
||||
private val mockNavHostController = createMockNavHostController()
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<OverlayNavEvent>()
|
||||
private val viewModel = mockk<OverlayNavViewModel> {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns MutableStateFlow(Unit)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
setContent {
|
||||
OverlayNavScreen(
|
||||
viewModel = viewModel,
|
||||
navController = mockNavHostController,
|
||||
onSplashScreenRemoved = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToCookieAcquisition should navigate to the cookie acquisition screen`() {
|
||||
mutableEventFlow.tryEmit(OverlayNavEvent.NavigateToCookieAcquisition)
|
||||
composeTestRule.runOnIdle {
|
||||
verify(exactly = 1) {
|
||||
mockNavHostController.navigate(route = CookieAcquisitionRoute, builder = any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToLocalNetworkAccess should navigate to the local network access screen`() {
|
||||
mutableEventFlow.tryEmit(OverlayNavEvent.NavigateToLocalNetworkAccess)
|
||||
composeTestRule.runOnIdle {
|
||||
verify(exactly = 1) {
|
||||
mockNavHostController.navigate(route = LocalNetworkAccessRoute, builder = any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on NavigateToAccessibilityDisclosure should navigate to the accessibility disclosure screen`() {
|
||||
mutableEventFlow.tryEmit(OverlayNavEvent.NavigateToAccessibilityDisclosure)
|
||||
composeTestRule.runOnIdle {
|
||||
verify(exactly = 1) {
|
||||
mockNavHostController.navigate(
|
||||
route = AccessibilityDisclosureRoute,
|
||||
builder = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.overlaynav
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.data.platform.manager.CookieAcquisitionRequestManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CookieAcquisitionRequest
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkPermissionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class OverlayNavViewModelTest : BaseViewModelTest() {
|
||||
private val mutableAccessibilityDisclaimerFlow = MutableStateFlow(true)
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { hasShownAccessibilityDisclaimerFlow } returns mutableAccessibilityDisclaimerFlow
|
||||
}
|
||||
private val mutableLocalNetworkAccessFlow = MutableStateFlow(false)
|
||||
private val networkPermissionManager: NetworkPermissionManager = mockk {
|
||||
every { isLocalNetworkAccessRequiredStateFlow } returns mutableLocalNetworkAccessFlow
|
||||
}
|
||||
private val mutableCookieAcquisitionFlow = MutableStateFlow<CookieAcquisitionRequest?>(null)
|
||||
private val cookieAcquisitionRequestManager: CookieAcquisitionRequestManager = mockk {
|
||||
every { cookieAcquisitionRequestFlow } returns mutableCookieAcquisitionFlow
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when accessibility disclaimer flow is false should emit NavigateToAccessibilityDisclosure`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
expectNoEvents()
|
||||
mutableAccessibilityDisclaimerFlow.value = false
|
||||
assertEquals(OverlayNavEvent.NavigateToAccessibilityDisclosure, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when local network access flow is true should emit NavigateToLocalNetworkAccess`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
expectNoEvents()
|
||||
mutableLocalNetworkAccessFlow.value = true
|
||||
assertEquals(OverlayNavEvent.NavigateToLocalNetworkAccess, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a cookie acquisition request is ready should emit NavigateToCookieAcquisition`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
expectNoEvents()
|
||||
mutableCookieAcquisitionFlow.value = CookieAcquisitionRequest(
|
||||
hostname = "vault.bitwarden.com",
|
||||
)
|
||||
assertEquals(OverlayNavEvent.NavigateToCookieAcquisition, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(): OverlayNavViewModel = OverlayNavViewModel(
|
||||
cookieAcquisitionRequestManager = cookieAcquisitionRequestManager,
|
||||
networkPermissionManager = networkPermissionManager,
|
||||
settingsRepository = settingsRepository,
|
||||
)
|
||||
}
|
||||
@@ -31,7 +31,6 @@ fun createMockNavHostController(): NavHostController =
|
||||
every { setViewModelStore(viewModelStore = any()) } just runs
|
||||
every { setLifecycleOwner(owner = any()) } just runs
|
||||
every { navigate(route = any<Any>(), navOptions = any()) } just runs
|
||||
every { navigate(route = any<Any>(), builder = any()) } just runs
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user