mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
PM-11188 show snackbar after import success. PM-13943 add relay for snackbar events across screen contexts. (#4152)
This commit is contained in:
parent
a1108889cb
commit
8b16135955
26 changed files with 505 additions and 59 deletions
|
@ -19,6 +19,7 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.other.navigateToOther
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.other.otherDestination
|
import com.x8bit.bitwarden.ui.platform.feature.settings.other.otherDestination
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.navigateToVaultSettings
|
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.navigateToVaultSettings
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.vaultSettingsDestination
|
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.vaultSettingsDestination
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
|
||||||
const val SETTINGS_GRAPH_ROUTE: String = "settings_graph"
|
const val SETTINGS_GRAPH_ROUTE: String = "settings_graph"
|
||||||
private const val SETTINGS_ROUTE: String = "settings"
|
private const val SETTINGS_ROUTE: String = "settings"
|
||||||
|
@ -35,7 +36,7 @@ fun NavGraphBuilder.settingsGraph(
|
||||||
onNavigateToPendingRequests: () -> Unit,
|
onNavigateToPendingRequests: () -> Unit,
|
||||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
) {
|
) {
|
||||||
navigation(
|
navigation(
|
||||||
startDestination = SETTINGS_ROUTE,
|
startDestination = SETTINGS_ROUTE,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavOptions
|
import androidx.navigation.NavOptions
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
|
||||||
private const val VAULT_SETTINGS_ROUTE = "vault_settings"
|
private const val VAULT_SETTINGS_ROUTE = "vault_settings"
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ fun NavGraphBuilder.vaultSettingsDestination(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToExportVault: () -> Unit,
|
onNavigateToExportVault: () -> Unit,
|
||||||
onNavigateToFolders: () -> Unit,
|
onNavigateToFolders: () -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
) {
|
) {
|
||||||
composableWithPushTransitions(
|
composableWithPushTransitions(
|
||||||
route = VAULT_SETTINGS_ROUTE,
|
route = VAULT_SETTINGS_ROUTE,
|
||||||
|
|
|
@ -33,9 +33,12 @@ import com.x8bit.bitwarden.ui.platform.components.card.actionCardExitAnimation
|
||||||
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow
|
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow
|
||||||
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenTextRow
|
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenTextRow
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarHost
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.rememberBitwardenSnackbarHostState
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the vault settings screen.
|
* Displays the vault settings screen.
|
||||||
|
@ -47,12 +50,13 @@ fun VaultSettingsScreen(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToExportVault: () -> Unit,
|
onNavigateToExportVault: () -> Unit,
|
||||||
onNavigateToFolders: () -> Unit,
|
onNavigateToFolders: () -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
viewModel: VaultSettingsViewModel = hiltViewModel(),
|
viewModel: VaultSettingsViewModel = hiltViewModel(),
|
||||||
intentManager: IntentManager = LocalIntentManager.current,
|
intentManager: IntentManager = LocalIntentManager.current,
|
||||||
) {
|
) {
|
||||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val snackbarHostState = rememberBitwardenSnackbarHostState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
EventsEffect(viewModel = viewModel) { event ->
|
EventsEffect(viewModel = viewModel) { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
|
@ -65,11 +69,13 @@ fun VaultSettingsScreen(
|
||||||
|
|
||||||
is VaultSettingsEvent.NavigateToImportVault -> {
|
is VaultSettingsEvent.NavigateToImportVault -> {
|
||||||
if (state.isNewImportLoginsFlowEnabled) {
|
if (state.isNewImportLoginsFlowEnabled) {
|
||||||
onNavigateToImportLogins()
|
onNavigateToImportLogins(SnackbarRelay.VAULT_SETTINGS_RELAY)
|
||||||
} else {
|
} else {
|
||||||
intentManager.launchUri(event.url.toUri())
|
intentManager.launchUri(event.url.toUri())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultSettingsEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +95,11 @@ fun VaultSettingsScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
snackbarHost = {
|
||||||
|
BitwardenSnackbarHost(
|
||||||
|
bitwardenHostState = snackbarHostState,
|
||||||
|
)
|
||||||
|
},
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
@ -7,6 +7,10 @@ import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.toBaseWebVaultImportUrl
|
import com.x8bit.bitwarden.data.platform.repository.util.toBaseWebVaultImportUrl
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.BackgroundEvent
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
@ -22,6 +26,7 @@ import javax.inject.Inject
|
||||||
class VaultSettingsViewModel @Inject constructor(
|
class VaultSettingsViewModel @Inject constructor(
|
||||||
environmentRepository: EnvironmentRepository,
|
environmentRepository: EnvironmentRepository,
|
||||||
featureFlagManager: FeatureFlagManager,
|
featureFlagManager: FeatureFlagManager,
|
||||||
|
snackbarRelayManager: SnackbarRelayManager,
|
||||||
private val firstTimeActionManager: FirstTimeActionManager,
|
private val firstTimeActionManager: FirstTimeActionManager,
|
||||||
) : BaseViewModel<VaultSettingsState, VaultSettingsEvent, VaultSettingsAction>(
|
) : BaseViewModel<VaultSettingsState, VaultSettingsEvent, VaultSettingsAction>(
|
||||||
initialState = run {
|
initialState = run {
|
||||||
|
@ -53,6 +58,14 @@ class VaultSettingsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
snackbarRelayManager
|
||||||
|
.getSnackbarDataFlow(SnackbarRelay.VAULT_SETTINGS_RELAY)
|
||||||
|
.map {
|
||||||
|
VaultSettingsAction.Internal.SnackbarDataReceived(it)
|
||||||
|
}
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleAction(action: VaultSettingsAction): Unit = when (action) {
|
override fun handleAction(action: VaultSettingsAction): Unit = when (action) {
|
||||||
|
@ -74,8 +87,18 @@ class VaultSettingsViewModel @Inject constructor(
|
||||||
is VaultSettingsAction.Internal.UserFirstTimeStateChanged -> {
|
is VaultSettingsAction.Internal.UserFirstTimeStateChanged -> {
|
||||||
handleUserFirstTimeStateChanged(action)
|
handleUserFirstTimeStateChanged(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultSettingsAction.Internal.SnackbarDataReceived -> {
|
||||||
|
handleSnackbarDataReceived(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSnackbarDataReceived(
|
||||||
|
action: VaultSettingsAction.Internal.SnackbarDataReceived,
|
||||||
|
) {
|
||||||
|
sendEvent(VaultSettingsEvent.ShowSnackbar(action.data))
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleImportLoginsCardDismissClicked() {
|
private fun handleImportLoginsCardDismissClicked() {
|
||||||
if (!state.shouldShowImportCard) return
|
if (!state.shouldShowImportCard) return
|
||||||
|
@ -166,6 +189,11 @@ sealed class VaultSettingsEvent {
|
||||||
data class ShowToast(
|
data class ShowToast(
|
||||||
val message: String,
|
val message: String,
|
||||||
) : VaultSettingsEvent()
|
) : VaultSettingsEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a snackbar with the given [data].
|
||||||
|
*/
|
||||||
|
data class ShowSnackbar(val data: BitwardenSnackbarData) : VaultSettingsEvent(), BackgroundEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -220,5 +248,10 @@ sealed class VaultSettingsAction {
|
||||||
data class UserFirstTimeStateChanged(
|
data class UserFirstTimeStateChanged(
|
||||||
val showImportLoginsCard: Boolean,
|
val showImportLoginsCard: Boolean,
|
||||||
) : Internal()
|
) : Internal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the snackbar data has been received.
|
||||||
|
*/
|
||||||
|
data class SnackbarDataReceived(val data: BitwardenSnackbarData) : Internal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,9 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
||||||
},
|
},
|
||||||
onNavigateToSetupUnlockScreen = { navController.navigateToSetupUnlockScreen() },
|
onNavigateToSetupUnlockScreen = { navController.navigateToSetupUnlockScreen() },
|
||||||
onNavigateToSetupAutoFillScreen = { navController.navigateToSetupAutoFillScreen() },
|
onNavigateToSetupAutoFillScreen = { navController.navigateToSetupAutoFillScreen() },
|
||||||
onNavigateToImportLogins = { navController.navigateToImportLoginsScreen() },
|
onNavigateToImportLogins = {
|
||||||
|
navController.navigateToImportLoginsScreen(snackbarRelay = it)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
deleteAccountDestination(
|
deleteAccountDestination(
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavOptions
|
import androidx.navigation.NavOptions
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
|
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +39,7 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
||||||
onNavigateToPasswordHistory: () -> Unit,
|
onNavigateToPasswordHistory: () -> Unit,
|
||||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
) {
|
) {
|
||||||
composableWithStayTransitions(
|
composableWithStayTransitions(
|
||||||
route = VAULT_UNLOCKED_NAV_BAR_ROUTE,
|
route = VAULT_UNLOCKED_NAV_BAR_ROUTE,
|
||||||
|
|
|
@ -44,6 +44,7 @@ import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraph
|
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraph
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.settingsGraph
|
import com.x8bit.bitwarden.ui.platform.feature.settings.settingsGraph
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.model.VaultUnlockedNavBarTab
|
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.model.VaultUnlockedNavBarTab
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.RootTransitionProviders
|
import com.x8bit.bitwarden.ui.platform.theme.RootTransitionProviders
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.generatorGraph
|
import com.x8bit.bitwarden.ui.tools.feature.generator.generatorGraph
|
||||||
|
@ -77,7 +78,7 @@ fun VaultUnlockedNavBarScreen(
|
||||||
onNavigateToPasswordHistory: () -> Unit,
|
onNavigateToPasswordHistory: () -> Unit,
|
||||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
) {
|
) {
|
||||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
@ -171,7 +172,7 @@ private fun VaultUnlockedNavBarScaffold(
|
||||||
navigateToPasswordHistory: () -> Unit,
|
navigateToPasswordHistory: () -> Unit,
|
||||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
) {
|
) {
|
||||||
var shouldDimNavBar by remember { mutableStateOf(false) }
|
var shouldDimNavBar by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManagerImpl
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManagerImpl
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManagerImpl
|
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManagerImpl
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManagerImpl
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
@ -32,4 +34,8 @@ class PlatformUiManagerModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideResourceManager(@ApplicationContext context: Context): ResourceManager =
|
fun provideResourceManager(@ApplicationContext context: Context): ResourceManager =
|
||||||
ResourceManagerImpl(context = context)
|
ResourceManagerImpl(context = context)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSnackbarRelayManager(): SnackbarRelayManager = SnackbarRelayManagerImpl()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.manager.snackbar
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models a relay key to be mapped to an instance of [BitwardenSnackbarData] being sent
|
||||||
|
* between producers and consumers of the data.
|
||||||
|
*/
|
||||||
|
enum class SnackbarRelay {
|
||||||
|
VAULT_SETTINGS_RELAY,
|
||||||
|
MY_VAULT_RELAY,
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.manager.snackbar
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager responsible for relaying snackbar data between a producer and consumer who may
|
||||||
|
* communicate with reference to a specific [SnackbarRelay].
|
||||||
|
*/
|
||||||
|
interface SnackbarRelayManager {
|
||||||
|
/**
|
||||||
|
* Called from a producer to send snackbar data to a consumer, the producer must
|
||||||
|
* specify the [relay] to send the data to.
|
||||||
|
*/
|
||||||
|
fun sendSnackbarData(data: BitwardenSnackbarData, relay: SnackbarRelay)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from a consumer to receive snackbar data from a producer, the consumer must specify
|
||||||
|
* the [relay] to receive the data from.
|
||||||
|
*/
|
||||||
|
fun getSnackbarDataFlow(relay: SnackbarRelay): Flow<BitwardenSnackbarData>
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.manager.snackbar
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of the [SnackbarRelayManager] interface.
|
||||||
|
*/
|
||||||
|
class SnackbarRelayManagerImpl : SnackbarRelayManager {
|
||||||
|
private val mutableSnackbarRelayMap =
|
||||||
|
mutableMapOf<SnackbarRelay, MutableSharedFlow<BitwardenSnackbarData?>>()
|
||||||
|
|
||||||
|
override fun sendSnackbarData(data: BitwardenSnackbarData, relay: SnackbarRelay) {
|
||||||
|
getSnackbarDataFlowInternal(relay).tryEmit(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSnackbarDataFlow(relay: SnackbarRelay): Flow<BitwardenSnackbarData> =
|
||||||
|
getSnackbarDataFlowInternal(relay)
|
||||||
|
.onCompletion {
|
||||||
|
// when the subscription is ended, remove the relay from the map.
|
||||||
|
mutableSnackbarRelayMap.remove(relay)
|
||||||
|
}
|
||||||
|
.filterNotNull()
|
||||||
|
|
||||||
|
private fun getSnackbarDataFlowInternal(
|
||||||
|
relay: SnackbarRelay,
|
||||||
|
): MutableSharedFlow<BitwardenSnackbarData?> =
|
||||||
|
mutableSnackbarRelayMap.getOrPut(relay) {
|
||||||
|
bufferedMutableSharedFlow(replay = 1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,39 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.importlogins
|
package com.x8bit.bitwarden.ui.vault.feature.importlogins
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavOptions
|
import androidx.navigation.NavOptions
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.navArgument
|
||||||
|
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
|
||||||
private const val IMPORT_LOGINS_ROUTE = "import-logins"
|
private const val IMPORT_LOGINS_PREFIX = "import-logins"
|
||||||
|
private const val IMPORT_LOGINS_NAV_ARG = "snackbarRelay"
|
||||||
|
private const val IMPORT_LOGINS_ROUTE = "$IMPORT_LOGINS_PREFIX/{$IMPORT_LOGINS_NAV_ARG}"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments for the [ImportLoginsScreen] using [SavedStateHandle].
|
||||||
|
*/
|
||||||
|
@OmitFromCoverage
|
||||||
|
data class ImportLoginsArgs(val snackBarRelay: SnackbarRelay) {
|
||||||
|
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||||
|
snackBarRelay = SnackbarRelay.valueOf(
|
||||||
|
requireNotNull(savedStateHandle[IMPORT_LOGINS_NAV_ARG]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to navigate to the import logins screen.
|
* Helper function to navigate to the import logins screen.
|
||||||
*/
|
*/
|
||||||
fun NavController.navigateToImportLoginsScreen(navOptions: NavOptions? = null) {
|
fun NavController.navigateToImportLoginsScreen(
|
||||||
navigate(route = IMPORT_LOGINS_ROUTE, navOptions = navOptions)
|
snackbarRelay: SnackbarRelay,
|
||||||
|
navOptions: NavOptions? = null,
|
||||||
|
) {
|
||||||
|
navigate(route = "$IMPORT_LOGINS_PREFIX/$snackbarRelay", navOptions = navOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,6 +44,12 @@ fun NavGraphBuilder.importLoginsScreenDestination(
|
||||||
) {
|
) {
|
||||||
composableWithSlideTransitions(
|
composableWithSlideTransitions(
|
||||||
route = IMPORT_LOGINS_ROUTE,
|
route = IMPORT_LOGINS_ROUTE,
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument(IMPORT_LOGINS_NAV_ARG) {
|
||||||
|
type = NavType.StringType
|
||||||
|
nullable = false
|
||||||
|
},
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
ImportLoginsScreen(
|
ImportLoginsScreen(
|
||||||
onNavigateBack = onNavigateBack,
|
onNavigateBack = onNavigateBack,
|
||||||
|
|
|
@ -58,6 +58,7 @@ import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.importlogins.components.ImportLoginsInstructionStep
|
import com.x8bit.bitwarden.ui.vault.feature.importlogins.components.ImportLoginsInstructionStep
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.importlogins.handlers.ImportLoginHandler
|
import com.x8bit.bitwarden.ui.vault.feature.importlogins.handlers.ImportLoginHandler
|
||||||
|
@ -627,6 +628,7 @@ private class ImportLoginsDialogContentPreviewProvider :
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = "vault.bitwarden.com",
|
currentWebVaultUrl = "vault.bitwarden.com",
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
ImportLoginsState(
|
ImportLoginsState(
|
||||||
dialogState = ImportLoginsState.DialogState.ImportLater,
|
dialogState = ImportLoginsState.DialogState.ImportLater,
|
||||||
|
@ -634,6 +636,7 @@ private class ImportLoginsDialogContentPreviewProvider :
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = "vault.bitwarden.com",
|
currentWebVaultUrl = "vault.bitwarden.com",
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.importlogins
|
package com.x8bit.bitwarden.ui.vault.feature.importlogins
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
|
@ -10,6 +11,9 @@ import com.x8bit.bitwarden.data.vault.repository.model.SyncVaultDataResult
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -21,9 +25,11 @@ import javax.inject.Inject
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ImportLoginsViewModel @Inject constructor(
|
class ImportLoginsViewModel @Inject constructor(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
private val vaultRepository: VaultRepository,
|
private val vaultRepository: VaultRepository,
|
||||||
private val firstTimeActionManager: FirstTimeActionManager,
|
private val firstTimeActionManager: FirstTimeActionManager,
|
||||||
private val environmentRepository: EnvironmentRepository,
|
private val environmentRepository: EnvironmentRepository,
|
||||||
|
private val snackbarRelayManager: SnackbarRelayManager,
|
||||||
) :
|
) :
|
||||||
BaseViewModel<ImportLoginsState, ImportLoginsEvent, ImportLoginsAction>(
|
BaseViewModel<ImportLoginsState, ImportLoginsEvent, ImportLoginsAction>(
|
||||||
initialState = run {
|
initialState = run {
|
||||||
|
@ -36,6 +42,7 @@ class ImportLoginsViewModel @Inject constructor(
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
// attempt to trim the scheme of the vault url
|
// attempt to trim the scheme of the vault url
|
||||||
currentWebVaultUrl = vaultUrl.toUriOrNull()?.host ?: vaultUrl,
|
currentWebVaultUrl = vaultUrl.toUriOrNull()?.host ?: vaultUrl,
|
||||||
|
snackbarRelay = ImportLoginsArgs(savedStateHandle).snackBarRelay,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -70,6 +77,13 @@ class ImportLoginsViewModel @Inject constructor(
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// instead of doing inline, this approach to avoid "MaxLineLength" suppression.
|
||||||
|
val snackbarData = BitwardenSnackbarData(
|
||||||
|
messageHeader = R.string.logins_imported.asText(),
|
||||||
|
message = R.string.remember_to_delete_your_imported_password_file_from_your_computer
|
||||||
|
.asText(),
|
||||||
|
)
|
||||||
|
snackbarRelayManager.sendSnackbarData(data = snackbarData, relay = state.snackbarRelay)
|
||||||
sendEvent(ImportLoginsEvent.NavigateBack)
|
sendEvent(ImportLoginsEvent.NavigateBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +227,7 @@ data class ImportLoginsState(
|
||||||
val isVaultSyncing: Boolean,
|
val isVaultSyncing: Boolean,
|
||||||
val showBottomSheet: Boolean,
|
val showBottomSheet: Boolean,
|
||||||
val currentWebVaultUrl: String,
|
val currentWebVaultUrl: String,
|
||||||
|
val snackbarRelay: SnackbarRelay,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Dialog states for the [ImportLoginsViewModel].
|
* Dialog states for the [ImportLoginsViewModel].
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavOptions
|
import androidx.navigation.NavOptions
|
||||||
import androidx.navigation.navigation
|
import androidx.navigation.navigation
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListing
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListing
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.vaultItemListingDestination
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.vaultItemListingDestination
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.navigateToVerificationCodeScreen
|
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.navigateToVerificationCodeScreen
|
||||||
|
@ -24,7 +25,7 @@ fun NavGraphBuilder.vaultGraph(
|
||||||
onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit,
|
onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit,
|
||||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
) {
|
) {
|
||||||
navigation(
|
navigation(
|
||||||
route = VAULT_GRAPH_ROUTE,
|
route = VAULT_GRAPH_ROUTE,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavOptions
|
import androidx.navigation.NavOptions
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
|
import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||||
|
|
||||||
const val VAULT_ROUTE: String = "vault"
|
const val VAULT_ROUTE: String = "vault"
|
||||||
|
@ -21,7 +22,7 @@ fun NavGraphBuilder.vaultDestination(
|
||||||
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
|
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
|
||||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
) {
|
) {
|
||||||
composableWithRootPushTransitions(
|
composableWithRootPushTransitions(
|
||||||
route = VAULT_ROUTE,
|
route = VAULT_ROUTE,
|
||||||
|
|
|
@ -55,6 +55,9 @@ import com.x8bit.bitwarden.ui.platform.components.model.TopAppBarDividerStyle
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenPullToRefreshState
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenPullToRefreshState
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarHost
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarHostState
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.rememberBitwardenSnackbarHostState
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||||
|
@ -63,6 +66,7 @@ import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
|
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
|
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.handlers.VaultHandlers
|
import com.x8bit.bitwarden.ui.vault.feature.vault.handlers.VaultHandlers
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||||
|
@ -83,7 +87,7 @@ fun VaultScreen(
|
||||||
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
|
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
|
||||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||||
onNavigateToImportLogins: () -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
exitManager: ExitManager = LocalExitManager.current,
|
exitManager: ExitManager = LocalExitManager.current,
|
||||||
intentManager: IntentManager = LocalIntentManager.current,
|
intentManager: IntentManager = LocalIntentManager.current,
|
||||||
permissionsManager: PermissionsManager = LocalPermissionsManager.current,
|
permissionsManager: PermissionsManager = LocalPermissionsManager.current,
|
||||||
|
@ -97,6 +101,7 @@ fun VaultScreen(
|
||||||
{ viewModel.trySendAction(VaultAction.RefreshPull) }
|
{ viewModel.trySendAction(VaultAction.RefreshPull) }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
val snackbarHostState = rememberBitwardenSnackbarHostState()
|
||||||
EventsEffect(viewModel = viewModel) { event ->
|
EventsEffect(viewModel = viewModel) { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
VaultEvent.NavigateToAddItemScreen -> onNavigateToVaultAddItemScreen()
|
VaultEvent.NavigateToAddItemScreen -> onNavigateToVaultAddItemScreen()
|
||||||
|
@ -124,7 +129,11 @@ fun VaultScreen(
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
VaultEvent.NavigateToImportLogins -> onNavigateToImportLogins()
|
VaultEvent.NavigateToImportLogins -> {
|
||||||
|
onNavigateToImportLogins(SnackbarRelay.MY_VAULT_RELAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VaultEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val vaultHandlers = remember(viewModel) { VaultHandlers.create(viewModel) }
|
val vaultHandlers = remember(viewModel) { VaultHandlers.create(viewModel) }
|
||||||
|
@ -137,6 +146,7 @@ fun VaultScreen(
|
||||||
pullToRefreshState = pullToRefreshState,
|
pullToRefreshState = pullToRefreshState,
|
||||||
vaultHandlers = vaultHandlers,
|
vaultHandlers = vaultHandlers,
|
||||||
onDimBottomNavBarRequest = onDimBottomNavBarRequest,
|
onDimBottomNavBarRequest = onDimBottomNavBarRequest,
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,6 +183,7 @@ private fun VaultScreenScaffold(
|
||||||
pullToRefreshState: BitwardenPullToRefreshState,
|
pullToRefreshState: BitwardenPullToRefreshState,
|
||||||
vaultHandlers: VaultHandlers,
|
vaultHandlers: VaultHandlers,
|
||||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||||
|
snackbarHostState: BitwardenSnackbarHostState,
|
||||||
) {
|
) {
|
||||||
var accountMenuVisible by rememberSaveable {
|
var accountMenuVisible by rememberSaveable {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
|
@ -261,6 +272,11 @@ private fun VaultScreenScaffold(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
snackbarHost = {
|
||||||
|
BitwardenSnackbarHost(
|
||||||
|
bitwardenHostState = snackbarHostState,
|
||||||
|
)
|
||||||
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = state.viewState.hasFab && !accountMenuVisible,
|
visible = state.viewState.hasFab && !accountMenuVisible,
|
||||||
|
|
|
@ -26,6 +26,7 @@ import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.BackgroundEvent
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||||
|
@ -33,6 +34,9 @@ import com.x8bit.bitwarden.ui.platform.base.util.hexToColor
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
|
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||||
|
@ -71,8 +75,9 @@ class VaultViewModel @Inject constructor(
|
||||||
private val policyManager: PolicyManager,
|
private val policyManager: PolicyManager,
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val vaultRepository: VaultRepository,
|
private val vaultRepository: VaultRepository,
|
||||||
private val featureFlagManager: FeatureFlagManager,
|
|
||||||
private val firstTimeActionManager: FirstTimeActionManager,
|
private val firstTimeActionManager: FirstTimeActionManager,
|
||||||
|
featureFlagManager: FeatureFlagManager,
|
||||||
|
snackbarRelayManager: SnackbarRelayManager,
|
||||||
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
||||||
initialState = run {
|
initialState = run {
|
||||||
val userState = requireNotNull(authRepository.userStateFlow.value)
|
val userState = requireNotNull(authRepository.userStateFlow.value)
|
||||||
|
@ -143,6 +148,14 @@ class VaultViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
snackbarRelayManager
|
||||||
|
.getSnackbarDataFlow(SnackbarRelay.MY_VAULT_RELAY)
|
||||||
|
.map {
|
||||||
|
VaultAction.Internal.SnackbarDataReceive(it)
|
||||||
|
}
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleAction(action: VaultAction) {
|
override fun handleAction(action: VaultAction) {
|
||||||
|
@ -458,9 +471,15 @@ class VaultViewModel @Inject constructor(
|
||||||
is VaultAction.Internal.ValidatePasswordResultReceive -> {
|
is VaultAction.Internal.ValidatePasswordResultReceive -> {
|
||||||
handleValidatePasswordResultReceive(action)
|
handleValidatePasswordResultReceive(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultAction.Internal.SnackbarDataReceive -> handleSnackbarDataReceive(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSnackbarDataReceive(action: VaultAction.Internal.SnackbarDataReceive) {
|
||||||
|
sendEvent(VaultEvent.ShowSnackbar(action.data))
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleGenerateTotpResultReceive(
|
private fun handleGenerateTotpResultReceive(
|
||||||
action: VaultAction.Internal.GenerateTotpResultReceive,
|
action: VaultAction.Internal.GenerateTotpResultReceive,
|
||||||
) {
|
) {
|
||||||
|
@ -1011,6 +1030,11 @@ sealed class VaultEvent {
|
||||||
* Show a toast with the given [message].
|
* Show a toast with the given [message].
|
||||||
*/
|
*/
|
||||||
data class ShowToast(val message: Text) : VaultEvent()
|
data class ShowToast(val message: Text) : VaultEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a snackbar with the given [data].
|
||||||
|
*/
|
||||||
|
data class ShowSnackbar(val data: BitwardenSnackbarData) : VaultEvent(), BackgroundEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1217,6 +1241,13 @@ sealed class VaultAction {
|
||||||
val overflowAction: ListingItemOverflowAction.VaultAction,
|
val overflowAction: ListingItemOverflowAction.VaultAction,
|
||||||
val result: ValidatePasswordResult,
|
val result: ValidatePasswordResult,
|
||||||
) : Internal()
|
) : Internal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a snackbar data was received.
|
||||||
|
*/
|
||||||
|
data class SnackbarDataReceive(
|
||||||
|
val data: BitwardenSnackbarData,
|
||||||
|
) : Internal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1073,4 +1073,6 @@ Do you want to switch to this account?</string>
|
||||||
<string name="bitwarden_tools">Bitwarden Tools</string>
|
<string name="bitwarden_tools">Bitwarden Tools</string>
|
||||||
<string name="got_it">Got it</string>
|
<string name="got_it">Got it</string>
|
||||||
<string name="no_logins_were_imported">No logins were imported</string>
|
<string name="no_logins_were_imported">No logins were imported</string>
|
||||||
|
<string name="logins_imported">Logins imported</string>
|
||||||
|
<string name="remember_to_delete_your_imported_password_file_from_your_computer">Remember to delete your imported password file from your computer</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -11,7 +11,10 @@ import androidx.compose.ui.test.performScrollTo
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -19,6 +22,7 @@ import io.mockk.runs
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
@ -56,7 +60,10 @@ class VaultSettingsScreenTest : BaseComposeTest() {
|
||||||
onNavigateToExportVault = { onNavigateToExportVaultCalled = true },
|
onNavigateToExportVault = { onNavigateToExportVaultCalled = true },
|
||||||
onNavigateToFolders = { onNavigateToFoldersCalled = true },
|
onNavigateToFolders = { onNavigateToFoldersCalled = true },
|
||||||
intentManager = intentManager,
|
intentManager = intentManager,
|
||||||
onNavigateToImportLogins = { onNavigateToImportLoginsCalled = true },
|
onNavigateToImportLogins = {
|
||||||
|
onNavigateToImportLoginsCalled = true
|
||||||
|
assertEquals(SnackbarRelay.VAULT_SETTINGS_RELAY, it)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,4 +220,11 @@ class VaultSettingsScreenTest : BaseComposeTest() {
|
||||||
viewModel.trySendAction(VaultSettingsAction.ImportLoginsCardCtaClick)
|
viewModel.trySendAction(VaultSettingsAction.ImportLoginsCardCtaClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when ShowSnackbar is sent snackbar should be displayed`() {
|
||||||
|
val data = BitwardenSnackbarData("message".asText())
|
||||||
|
mutableEventFlow.tryEmit(VaultSettingsEvent.ShowSnackbar(data))
|
||||||
|
composeTestRule.onNodeWithText("message").assertIsDisplayed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@ import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManagerImpl
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -34,6 +38,8 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
||||||
every { storeShowImportLoginsSettingsBadge(any()) } just runs
|
every { storeShowImportLoginsSettingsBadge(any()) } just runs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val snackbarRelayManager = SnackbarRelayManagerImpl()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `BackClick should emit NavigateBack`() = runTest {
|
fun `BackClick should emit NavigateBack`() = runTest {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
|
@ -136,10 +142,24 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `SnackbarDataReceived action should send snackbar event`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val expectedSnackbarData = BitwardenSnackbarData(message = "test message".asText())
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
snackbarRelayManager.sendSnackbarData(
|
||||||
|
data = expectedSnackbarData,
|
||||||
|
relay = SnackbarRelay.VAULT_SETTINGS_RELAY,
|
||||||
|
)
|
||||||
|
assertEquals(VaultSettingsEvent.ShowSnackbar(expectedSnackbarData), awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createViewModel(): VaultSettingsViewModel = VaultSettingsViewModel(
|
private fun createViewModel(): VaultSettingsViewModel = VaultSettingsViewModel(
|
||||||
environmentRepository = environmentRepository,
|
environmentRepository = environmentRepository,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
firstTimeActionManager = firstTimeActionManager,
|
firstTimeActionManager = firstTimeActionManager,
|
||||||
|
snackbarRelayManager = snackbarRelayManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.manager.snackbar
|
||||||
|
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import app.cash.turbine.turbineScope
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
|
||||||
|
class SnackbarRelayManagerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Relay is completed successfully when consumer registers first and event is sent`() =
|
||||||
|
runTest {
|
||||||
|
val relayManager = SnackbarRelayManagerImpl()
|
||||||
|
val relay = SnackbarRelay.MY_VAULT_RELAY
|
||||||
|
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||||
|
val consumer = relayManager.getSnackbarDataFlow(relay)
|
||||||
|
|
||||||
|
consumer.test {
|
||||||
|
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||||
|
assertEquals(
|
||||||
|
expectedData,
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Relay is completed successfully when consumer registers second and event is sent`() =
|
||||||
|
runTest {
|
||||||
|
val relayManager = SnackbarRelayManagerImpl()
|
||||||
|
val relay = SnackbarRelay.MY_VAULT_RELAY
|
||||||
|
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||||
|
// producer code
|
||||||
|
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||||
|
relayManager.getSnackbarDataFlow(relay).test {
|
||||||
|
assertEquals(
|
||||||
|
expectedData,
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `When relay is specified by producer only send data to that relay`() =
|
||||||
|
runTest {
|
||||||
|
val relayManager = SnackbarRelayManagerImpl()
|
||||||
|
val relay1 = SnackbarRelay.MY_VAULT_RELAY
|
||||||
|
val relay2 = SnackbarRelay.VAULT_SETTINGS_RELAY
|
||||||
|
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||||
|
turbineScope {
|
||||||
|
val consumer1 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope)
|
||||||
|
val consumer2 = relayManager.getSnackbarDataFlow(relay2).testIn(backgroundScope)
|
||||||
|
relayManager.sendSnackbarData(data = expectedData, relay = relay1)
|
||||||
|
consumer2.expectNoEvents()
|
||||||
|
assertEquals(
|
||||||
|
expectedData,
|
||||||
|
consumer1.awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `When multiple consumers are registered to the same relay, send data to all consumers`() =
|
||||||
|
runTest {
|
||||||
|
val relayManager = SnackbarRelayManagerImpl()
|
||||||
|
val relay1 = SnackbarRelay.MY_VAULT_RELAY
|
||||||
|
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||||
|
turbineScope {
|
||||||
|
val consumer1 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope)
|
||||||
|
relayManager.sendSnackbarData(data = expectedData, relay = relay1)
|
||||||
|
assertEquals(
|
||||||
|
expectedData,
|
||||||
|
consumer1.awaitItem(),
|
||||||
|
)
|
||||||
|
val consumer2 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope)
|
||||||
|
assertEquals(
|
||||||
|
expectedData,
|
||||||
|
consumer2.awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `When multiple consumers are register to the same relay, and one is completed before the other the second consumer registers should not receive any emissions`() =
|
||||||
|
runTest {
|
||||||
|
val relayManager = SnackbarRelayManagerImpl()
|
||||||
|
val relay1 = SnackbarRelay.MY_VAULT_RELAY
|
||||||
|
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||||
|
turbineScope {
|
||||||
|
val consumer1 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope)
|
||||||
|
relayManager.sendSnackbarData(data = expectedData, relay = relay1)
|
||||||
|
assertEquals(
|
||||||
|
expectedData,
|
||||||
|
consumer1.awaitItem(),
|
||||||
|
)
|
||||||
|
consumer1.cancel()
|
||||||
|
val consumer2 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope)
|
||||||
|
consumer2.expectNoEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import com.x8bit.bitwarden.data.util.advanceTimeByAndRunCurrent
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
|
@ -491,4 +492,5 @@ private val DEFAULT_STATE = ImportLoginsState(
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = "vault.bitwarden.com",
|
currentWebVaultUrl = "vault.bitwarden.com",
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.importlogins
|
package com.x8bit.bitwarden.ui.vault.feature.importlogins
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
|
@ -10,15 +11,18 @@ import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.SyncVaultDataResult
|
import com.x8bit.bitwarden.data.vault.repository.model.SyncVaultDataResult
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManagerImpl
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.runs
|
|
||||||
import io.mockk.verify
|
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.runs
|
||||||
import io.mockk.unmockkStatic
|
import io.mockk.unmockkStatic
|
||||||
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
@ -55,6 +59,10 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
unmockkStatic(Uri::parse)
|
unmockkStatic(Uri::parse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val snackbarRelayManager: SnackbarRelayManagerImpl = mockk() {
|
||||||
|
coEvery { sendSnackbarData(any(), any()) } just runs
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `initial state is correct`() {
|
fun `initial state is correct`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
|
@ -75,6 +83,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -91,6 +100,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -112,6 +122,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -123,6 +134,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -146,6 +158,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
stateFlow.awaitItem(),
|
stateFlow.awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -157,6 +170,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
stateFlow.awaitItem(),
|
stateFlow.awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -187,6 +201,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -198,6 +213,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -239,6 +255,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -255,6 +272,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -271,6 +289,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -291,6 +310,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -312,6 +332,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = true,
|
isVaultSyncing = true,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -340,6 +361,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = true,
|
isVaultSyncing = true,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -360,6 +382,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = true,
|
showBottomSheet = true,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -390,6 +413,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = true,
|
isVaultSyncing = true,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -400,6 +424,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -418,6 +443,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -441,6 +467,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
@ -451,8 +478,10 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `SuccessfulSyncAcknowledged should hide bottom sheet and send NavigateBack`() = runTest {
|
fun `SuccessfulSyncAcknowledged should hide bottom sheet and send NavigateBack event and send Snackbar data through snackbar manager`() =
|
||||||
|
runTest {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.stateEventFlow(backgroundScope = backgroundScope) { stateFlow, eventFlow ->
|
viewModel.stateEventFlow(backgroundScope = backgroundScope) { stateFlow, eventFlow ->
|
||||||
// Initial state
|
// Initial state
|
||||||
|
@ -465,6 +494,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = true,
|
isVaultSyncing = true,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
stateFlow.awaitItem(),
|
stateFlow.awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -475,6 +505,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = true,
|
showBottomSheet = true,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
stateFlow.awaitItem(),
|
stateFlow.awaitItem(),
|
||||||
)
|
)
|
||||||
|
@ -486,17 +517,37 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
),
|
),
|
||||||
stateFlow.awaitItem(),
|
stateFlow.awaitItem(),
|
||||||
)
|
)
|
||||||
assertEquals(ImportLoginsEvent.NavigateBack, eventFlow.awaitItem())
|
assertEquals(ImportLoginsEvent.NavigateBack, eventFlow.awaitItem())
|
||||||
}
|
}
|
||||||
|
val expectedSnackbarData = BitwardenSnackbarData(
|
||||||
|
messageHeader = R.string.logins_imported.asText(),
|
||||||
|
message = R.string.remember_to_delete_your_imported_password_file_from_your_computer
|
||||||
|
.asText(),
|
||||||
|
)
|
||||||
|
verify {
|
||||||
|
snackbarRelayManager.sendSnackbarData(
|
||||||
|
data = expectedSnackbarData,
|
||||||
|
relay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createViewModel(): ImportLoginsViewModel = ImportLoginsViewModel(
|
private fun createViewModel(
|
||||||
|
snackbarRelay: SnackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
|
): ImportLoginsViewModel = ImportLoginsViewModel(
|
||||||
|
savedStateHandle = SavedStateHandle(
|
||||||
|
mapOf(
|
||||||
|
"snackbarRelay" to snackbarRelay.name,
|
||||||
|
),
|
||||||
|
),
|
||||||
vaultRepository = vaultRepository,
|
vaultRepository = vaultRepository,
|
||||||
firstTimeActionManager = firstTimeActionManager,
|
firstTimeActionManager = firstTimeActionManager,
|
||||||
environmentRepository = environmentRepository,
|
environmentRepository = environmentRepository,
|
||||||
|
snackbarRelayManager = snackbarRelayManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,4 +559,5 @@ private val DEFAULT_STATE = ImportLoginsState(
|
||||||
isVaultSyncing = false,
|
isVaultSyncing = false,
|
||||||
showBottomSheet = false,
|
showBottomSheet = false,
|
||||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||||
|
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,9 +24,11 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
|
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager
|
import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
||||||
import com.x8bit.bitwarden.ui.util.assertLogoutConfirmationDialogIsDisplayed
|
import com.x8bit.bitwarden.ui.util.assertLogoutConfirmationDialogIsDisplayed
|
||||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||||
|
@ -91,7 +93,10 @@ class VaultScreenTest : BaseComposeTest() {
|
||||||
onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true },
|
onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true },
|
||||||
onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = true },
|
onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = true },
|
||||||
onNavigateToSearchVault = { onNavigateToSearchScreen = true },
|
onNavigateToSearchVault = { onNavigateToSearchScreen = true },
|
||||||
onNavigateToImportLogins = { onNavigateToImportLoginsCalled = true },
|
onNavigateToImportLogins = {
|
||||||
|
onNavigateToImportLoginsCalled = true
|
||||||
|
assertEquals(SnackbarRelay.MY_VAULT_RELAY, it)
|
||||||
|
},
|
||||||
exitManager = exitManager,
|
exitManager = exitManager,
|
||||||
intentManager = intentManager,
|
intentManager = intentManager,
|
||||||
permissionsManager = permissionsManager,
|
permissionsManager = permissionsManager,
|
||||||
|
@ -1195,6 +1200,13 @@ class VaultScreenTest : BaseComposeTest() {
|
||||||
mutableEventFlow.tryEmit(VaultEvent.NavigateToImportLogins)
|
mutableEventFlow.tryEmit(VaultEvent.NavigateToImportLogins)
|
||||||
assertTrue(onNavigateToImportLoginsCalled)
|
assertTrue(onNavigateToImportLoginsCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when ShowSnackbar is sent snackbar should be displayed`() {
|
||||||
|
val data = BitwardenSnackbarData("message".asText())
|
||||||
|
mutableEventFlow.tryEmit(VaultEvent.ShowSnackbar(data))
|
||||||
|
composeTestRule.onNodeWithText("message").assertIsDisplayed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ACTIVE_ACCOUNT_SUMMARY = AccountSummary(
|
private val ACTIVE_ACCOUNT_SUMMARY = AccountSummary(
|
||||||
|
|
|
@ -33,6 +33,9 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManagerImpl
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||||
|
@ -62,6 +65,8 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||||
ZoneOffset.UTC,
|
ZoneOffset.UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val snackbarRelayManager = SnackbarRelayManagerImpl()
|
||||||
|
|
||||||
private val clipboardManager: BitwardenClipboardManager = mockk {
|
private val clipboardManager: BitwardenClipboardManager = mockk {
|
||||||
every { setText(any<String>()) } just runs
|
every { setText(any<String>()) } just runs
|
||||||
}
|
}
|
||||||
|
@ -1619,6 +1624,19 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when SnackbarRelay flow updates, snackbar is shown`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val expectedSnackbarData = BitwardenSnackbarData(message = "test message".asText())
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
snackbarRelayManager.sendSnackbarData(
|
||||||
|
data = expectedSnackbarData,
|
||||||
|
relay = SnackbarRelay.MY_VAULT_RELAY,
|
||||||
|
)
|
||||||
|
assertEquals(VaultEvent.ShowSnackbar(expectedSnackbarData), awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createViewModel(): VaultViewModel =
|
private fun createViewModel(): VaultViewModel =
|
||||||
VaultViewModel(
|
VaultViewModel(
|
||||||
authRepository = authRepository,
|
authRepository = authRepository,
|
||||||
|
@ -1630,6 +1648,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||||
organizationEventManager = organizationEventManager,
|
organizationEventManager = organizationEventManager,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
firstTimeActionManager = firstTimeActionManager,
|
firstTimeActionManager = firstTimeActionManager,
|
||||||
|
snackbarRelayManager = snackbarRelayManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue