mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 04:39:19 -06:00
Update the SnackbarRelayManager (#5317)
This commit is contained in:
parent
e1cd813445
commit
beb4c533c8
@ -27,7 +27,6 @@ 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.vault.navigateToVaultSettings
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.vaultSettingsDestination
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.platform.util.toObjectRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@ -95,7 +94,7 @@ fun NavGraphBuilder.settingsGraph(
|
||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||
onNavigateToFlightRecorder: () -> Unit,
|
||||
onNavigateToRecordedLogs: () -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
) {
|
||||
navigation<SettingsGraphRoute>(
|
||||
startDestination = SettingsRoute.Standard,
|
||||
|
||||
@ -4,7 +4,6 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
@ -20,7 +19,7 @@ fun NavGraphBuilder.vaultSettingsDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToExportVault: () -> Unit,
|
||||
onNavigateToFolders: () -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions<VaultSettingsRoute> {
|
||||
VaultSettingsScreen(
|
||||
|
||||
@ -42,7 +42,6 @@ import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarHost
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.rememberBitwardenSnackbarHostState
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
|
||||
/**
|
||||
* Displays the vault settings screen.
|
||||
@ -54,7 +53,7 @@ fun VaultSettingsScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToExportVault: () -> Unit,
|
||||
onNavigateToFolders: () -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
viewModel: VaultSettingsViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
) {
|
||||
@ -73,7 +72,7 @@ fun VaultSettingsScreen(
|
||||
|
||||
is VaultSettingsEvent.NavigateToImportVault -> {
|
||||
if (state.isNewImportLoginsFlowEnabled) {
|
||||
onNavigateToImportLogins(SnackbarRelay.VAULT_SETTINGS_RELAY)
|
||||
onNavigateToImportLogins()
|
||||
} else {
|
||||
intentManager.launchUri(event.url.toUri())
|
||||
}
|
||||
|
||||
@ -60,10 +60,8 @@ class VaultSettingsViewModel @Inject constructor(
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
snackbarRelayManager
|
||||
.getSnackbarDataFlow(SnackbarRelay.VAULT_SETTINGS_RELAY)
|
||||
.map {
|
||||
VaultSettingsAction.Internal.SnackbarDataReceived(it)
|
||||
}
|
||||
.getSnackbarDataFlow(SnackbarRelay.LOGINS_IMPORTED)
|
||||
.map { VaultSettingsAction.Internal.SnackbarDataReceived(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
@ -113,9 +113,7 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
||||
},
|
||||
onNavigateToSetupUnlockScreen = { navController.navigateToSetupUnlockScreen() },
|
||||
onNavigateToSetupAutoFillScreen = { navController.navigateToSetupAutoFillScreen() },
|
||||
onNavigateToImportLogins = {
|
||||
navController.navigateToImportLoginsScreen(snackbarRelay = it)
|
||||
},
|
||||
onNavigateToImportLogins = { navController.navigateToImportLoginsScreen() },
|
||||
onNavigateToAddFolderScreen = {
|
||||
navController.navigateToFolderAddEdit(
|
||||
folderAddEditType = FolderAddEditType.AddItem,
|
||||
|
||||
@ -5,7 +5,6 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.ui.platform.base.util.composableWithStayTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.AddEditSendRoute
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.viewsend.ViewSendRoute
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
@ -46,7 +45,7 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||
onNavigateToFlightRecorder: () -> Unit,
|
||||
onNavigateToRecordedLogs: () -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
onNavigateToAddFolderScreen: (selectedFolderName: String?) -> Unit,
|
||||
) {
|
||||
composableWithStayTransitions<VaultUnlockedNavbarRoute> {
|
||||
|
||||
@ -31,7 +31,6 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraph
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraphRoot
|
||||
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.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.generatorGraph
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.navigateToGeneratorGraph
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.AddEditSendRoute
|
||||
@ -71,7 +70,7 @@ fun VaultUnlockedNavBarScreen(
|
||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||
onNavigateToFlightRecorder: () -> Unit,
|
||||
onNavigateToRecordedLogs: () -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
@ -178,7 +177,7 @@ private fun VaultUnlockedNavBarScaffold(
|
||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||
onNavigateToFlightRecorder: () -> Unit,
|
||||
onNavigateToRecordedLogs: () -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
) {
|
||||
var shouldDimNavBar by rememberSaveable { mutableStateOf(value = false) }
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.ui.platform.manager.di
|
||||
|
||||
import android.content.Context
|
||||
import com.bitwarden.data.manager.DispatcherManager
|
||||
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.resource.ResourceManager
|
||||
@ -40,5 +41,9 @@ class PlatformUiManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSnackbarRelayManager(): SnackbarRelayManager = SnackbarRelayManagerImpl()
|
||||
fun provideSnackbarRelayManager(
|
||||
dispatcherManager: DispatcherManager,
|
||||
): SnackbarRelayManager = SnackbarRelayManagerImpl(
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
}
|
||||
|
||||
@ -9,6 +9,6 @@ import kotlinx.serialization.Serializable
|
||||
*/
|
||||
@Serializable
|
||||
enum class SnackbarRelay {
|
||||
VAULT_SETTINGS_RELAY,
|
||||
MY_VAULT_RELAY,
|
||||
LOGINS_IMPORTED,
|
||||
SEND_DELETED,
|
||||
}
|
||||
|
||||
@ -19,9 +19,4 @@ interface SnackbarRelayManager {
|
||||
* the [relay] to receive the data from.
|
||||
*/
|
||||
fun getSnackbarDataFlow(relay: SnackbarRelay): Flow<BitwardenSnackbarData>
|
||||
|
||||
/**
|
||||
* Clears the buffer for the given [relay].
|
||||
*/
|
||||
fun clearRelayBuffer(relay: SnackbarRelay)
|
||||
}
|
||||
|
||||
@ -1,41 +1,77 @@
|
||||
package com.x8bit.bitwarden.ui.platform.manager.snackbar
|
||||
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.core.data.repository.util.emitWhenSubscribedTo
|
||||
import com.bitwarden.data.manager.DispatcherManager
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* The default implementation of the [SnackbarRelayManager] interface.
|
||||
*/
|
||||
class SnackbarRelayManagerImpl : SnackbarRelayManager {
|
||||
private val mutableSnackbarRelayMap =
|
||||
mutableMapOf<SnackbarRelay, MutableSharedFlow<BitwardenSnackbarData?>>()
|
||||
class SnackbarRelayManagerImpl(
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : SnackbarRelayManager {
|
||||
private val unconfinedScope = CoroutineScope(context = dispatcherManager.unconfined)
|
||||
private val snackbarSharedFlow = SnackbarLastSubscriberMutableSharedFlow()
|
||||
|
||||
override fun sendSnackbarData(data: BitwardenSnackbarData, relay: SnackbarRelay) {
|
||||
getSnackbarDataFlowInternal(relay).tryEmit(data)
|
||||
unconfinedScope.launch {
|
||||
snackbarSharedFlow.emitWhenSubscribedTo(
|
||||
value = SnackbarDataAndRelay(
|
||||
relay = relay,
|
||||
data = 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()
|
||||
snackbarSharedFlow
|
||||
.generateFlowFor(relay = relay)
|
||||
.map { it.data }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun clearRelayBuffer(relay: SnackbarRelay) {
|
||||
getSnackbarDataFlowInternal(relay).resetReplayCache()
|
||||
/**
|
||||
* A wrapper for the [BitwardenSnackbarData] payload and [SnackbarRelay] associated with it.
|
||||
*/
|
||||
private data class SnackbarDataAndRelay(
|
||||
val relay: SnackbarRelay,
|
||||
val data: BitwardenSnackbarData,
|
||||
)
|
||||
|
||||
/**
|
||||
* Helper class that ensures that only the last subscriber to a specific relay gets the Snackbar
|
||||
* data.
|
||||
*/
|
||||
@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
|
||||
private class SnackbarLastSubscriberMutableSharedFlow(
|
||||
private val source: MutableSharedFlow<SnackbarDataAndRelay> = MutableSharedFlow(),
|
||||
) : MutableSharedFlow<SnackbarDataAndRelay> by source {
|
||||
private val mutableRelayUuidMap: MutableMap<SnackbarRelay, MutableList<UUID>> = mutableMapOf()
|
||||
|
||||
fun generateFlowFor(
|
||||
relay: SnackbarRelay,
|
||||
): Flow<SnackbarDataAndRelay> {
|
||||
lateinit var uuid: UUID
|
||||
return source
|
||||
.onSubscription {
|
||||
uuid = UUID.randomUUID().also { getUuidStack(relay = relay).add(element = it) }
|
||||
}
|
||||
.onCompletion { getUuidStack(relay = relay).remove(element = uuid) }
|
||||
.filter { it.relay == relay }
|
||||
.filter { getUuidStack(relay = relay).last() == uuid }
|
||||
}
|
||||
|
||||
private fun getSnackbarDataFlowInternal(
|
||||
private fun getUuidStack(
|
||||
relay: SnackbarRelay,
|
||||
): MutableSharedFlow<BitwardenSnackbarData?> =
|
||||
mutableSnackbarRelayMap.getOrPut(relay) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
): MutableList<UUID> = mutableRelayUuidMap.getOrPut(key = relay) { mutableListOf() }
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.toRoute
|
||||
import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -13,31 +12,20 @@ import kotlinx.serialization.Serializable
|
||||
* The type-safe route for the import logins screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class ImportLoginsRoute(
|
||||
val snackbarRelay: SnackbarRelay,
|
||||
)
|
||||
data object ImportLoginsRoute
|
||||
|
||||
/**
|
||||
* Arguments for the [ImportLoginsScreen] using [SavedStateHandle].
|
||||
*/
|
||||
data class ImportLoginsArgs(val snackBarRelay: SnackbarRelay)
|
||||
|
||||
/**
|
||||
* Constructs a [ImportLoginsArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toImportLoginsArgs(): ImportLoginsArgs {
|
||||
val route = this.toRoute<ImportLoginsRoute>()
|
||||
return ImportLoginsArgs(snackBarRelay = route.snackbarRelay)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to navigate to the import logins screen.
|
||||
*/
|
||||
fun NavController.navigateToImportLoginsScreen(
|
||||
snackbarRelay: SnackbarRelay,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(route = ImportLoginsRoute(snackbarRelay = snackbarRelay), navOptions = navOptions)
|
||||
navigate(route = ImportLoginsRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -59,7 +59,6 @@ import com.x8bit.bitwarden.ui.platform.components.model.ContentBlockData
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
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.rememberImportLoginHandler
|
||||
@ -561,14 +560,12 @@ private class ImportLoginsDialogContentPreviewProvider :
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = "vault.bitwarden.com",
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
ImportLoginsState(
|
||||
dialogState = ImportLoginsState.DialogState.ImportLater,
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = "vault.bitwarden.com",
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.importlogins
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.util.Text
|
||||
@ -25,26 +24,23 @@ import javax.inject.Inject
|
||||
@Suppress("TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class ImportLoginsViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val firstTimeActionManager: FirstTimeActionManager,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val snackbarRelayManager: SnackbarRelayManager,
|
||||
) :
|
||||
BaseViewModel<ImportLoginsState, ImportLoginsEvent, ImportLoginsAction>(
|
||||
initialState = run {
|
||||
val vaultUrl = environmentRepository.environment.environmentUrlData.webVault
|
||||
?: environmentRepository.environment.environmentUrlData.base
|
||||
ImportLoginsState(
|
||||
dialogState = null,
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
// attempt to trim the scheme of the vault url
|
||||
currentWebVaultUrl = vaultUrl.toUriOrNull()?.host ?: vaultUrl,
|
||||
snackbarRelay = savedStateHandle.toImportLoginsArgs().snackBarRelay,
|
||||
)
|
||||
},
|
||||
) {
|
||||
) : BaseViewModel<ImportLoginsState, ImportLoginsEvent, ImportLoginsAction>(
|
||||
initialState = run {
|
||||
val vaultUrl = environmentRepository.environment.environmentUrlData.webVault
|
||||
?: environmentRepository.environment.environmentUrlData.base
|
||||
ImportLoginsState(
|
||||
dialogState = null,
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
// attempt to trim the scheme of the vault url
|
||||
currentWebVaultUrl = vaultUrl.toUriOrNull()?.host ?: vaultUrl,
|
||||
)
|
||||
},
|
||||
) {
|
||||
override fun handleAction(action: ImportLoginsAction) {
|
||||
when (action) {
|
||||
ImportLoginsAction.ConfirmGetStarted -> handleConfirmGetStarted()
|
||||
@ -76,13 +72,15 @@ class ImportLoginsViewModel @Inject constructor(
|
||||
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 = BitwardenSnackbarData(
|
||||
messageHeader = R.string.logins_imported.asText(),
|
||||
message = R.string
|
||||
.remember_to_delete_your_imported_password_file_from_your_computer
|
||||
.asText(),
|
||||
),
|
||||
relay = SnackbarRelay.LOGINS_IMPORTED,
|
||||
)
|
||||
snackbarRelayManager.sendSnackbarData(data = snackbarData, relay = state.snackbarRelay)
|
||||
sendEvent(ImportLoginsEvent.NavigateBack)
|
||||
}
|
||||
|
||||
@ -221,7 +219,6 @@ data class ImportLoginsState(
|
||||
val viewState: ViewState,
|
||||
val showBottomSheet: Boolean,
|
||||
val currentWebVaultUrl: String,
|
||||
val snackbarRelay: SnackbarRelay,
|
||||
) {
|
||||
/**
|
||||
* Dialog states for the [ImportLoginsViewModel].
|
||||
|
||||
@ -5,7 +5,6 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.navigation
|
||||
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.addedit.VaultAddEditArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListing
|
||||
@ -31,7 +30,7 @@ fun NavGraphBuilder.vaultGraph(
|
||||
onNavigateToVaultEditItemScreen: (args: VaultAddEditArgs) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
onNavigateToAboutScreen: () -> Unit,
|
||||
) {
|
||||
|
||||
@ -5,7 +5,6 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
|
||||
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.addedit.VaultAddEditArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
@ -29,7 +28,7 @@ fun NavGraphBuilder.vaultDestination(
|
||||
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
onNavigateToAboutScreen: () -> Unit,
|
||||
) {
|
||||
|
||||
@ -63,7 +63,6 @@ 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.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.review.AppReviewManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.vault.components.VaultItemSelectionDialog
|
||||
import com.x8bit.bitwarden.ui.vault.components.model.CreateVaultItemType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
@ -92,7 +91,7 @@ fun VaultScreen(
|
||||
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
onNavigateToAboutScreen: () -> Unit,
|
||||
exitManager: ExitManager = LocalExitManager.current,
|
||||
@ -170,10 +169,7 @@ fun VaultScreen(
|
||||
.show()
|
||||
}
|
||||
|
||||
VaultEvent.NavigateToImportLogins -> {
|
||||
onNavigateToImportLogins(SnackbarRelay.MY_VAULT_RELAY)
|
||||
}
|
||||
|
||||
VaultEvent.NavigateToImportLogins -> onNavigateToImportLogins()
|
||||
is VaultEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||
VaultEvent.PromptForAppReview -> {
|
||||
launchPrompt.invoke()
|
||||
|
||||
@ -86,11 +86,11 @@ class VaultViewModel @Inject constructor(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val firstTimeActionManager: FirstTimeActionManager,
|
||||
private val snackbarRelayManager: SnackbarRelayManager,
|
||||
private val reviewPromptManager: ReviewPromptManager,
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val networkConnectionManager: NetworkConnectionManager,
|
||||
snackbarRelayManager: SnackbarRelayManager,
|
||||
featureFlagManager: FeatureFlagManager,
|
||||
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
||||
initialState = run {
|
||||
val userState = requireNotNull(authRepository.userStateFlow.value)
|
||||
@ -166,7 +166,7 @@ class VaultViewModel @Inject constructor(
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
snackbarRelayManager
|
||||
.getSnackbarDataFlow(SnackbarRelay.MY_VAULT_RELAY)
|
||||
.getSnackbarDataFlow(SnackbarRelay.LOGINS_IMPORTED)
|
||||
.map { VaultAction.Internal.SnackbarDataReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
@ -368,9 +368,6 @@ class VaultViewModel @Inject constructor(
|
||||
SwitchAccountResult.AccountSwitched -> true
|
||||
SwitchAccountResult.NoChange -> false
|
||||
}
|
||||
if (isSwitchingAccounts) {
|
||||
snackbarRelayManager.clearRelayBuffer(SnackbarRelay.MY_VAULT_RELAY)
|
||||
}
|
||||
mutableStateFlow.update {
|
||||
it.copy(isSwitchingAccounts = isSwitchingAccounts)
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||
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.snackbar.SnackbarRelay
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
@ -23,7 +22,6 @@ import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
@ -62,10 +60,7 @@ class VaultSettingsScreenTest : BitwardenComposeTest() {
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
onNavigateToExportVault = { onNavigateToExportVaultCalled = true },
|
||||
onNavigateToFolders = { onNavigateToFoldersCalled = true },
|
||||
onNavigateToImportLogins = {
|
||||
onNavigateToImportLoginsCalled = true
|
||||
assertEquals(SnackbarRelay.VAULT_SETTINGS_RELAY, it)
|
||||
},
|
||||
onNavigateToImportLogins = { onNavigateToImportLoginsCalled = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
@ -10,7 +11,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
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.platform.manager.snackbar.SnackbarRelayManager
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
@ -38,7 +39,12 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
||||
every { storeShowImportLoginsSettingsBadge(any()) } just runs
|
||||
}
|
||||
|
||||
private val snackbarRelayManager = SnackbarRelayManagerImpl()
|
||||
private val mutableSnackbarSharedFlow = bufferedMutableSharedFlow<BitwardenSnackbarData>()
|
||||
private val snackbarRelayManager = mockk<SnackbarRelayManager> {
|
||||
every {
|
||||
getSnackbarDataFlow(SnackbarRelay.LOGINS_IMPORTED)
|
||||
} returns mutableSnackbarSharedFlow
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BackClick should emit NavigateBack`() = runTest {
|
||||
@ -147,10 +153,7 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
||||
val viewModel = createViewModel()
|
||||
val expectedSnackbarData = BitwardenSnackbarData(message = "test message".asText())
|
||||
viewModel.eventFlow.test {
|
||||
snackbarRelayManager.sendSnackbarData(
|
||||
data = expectedSnackbarData,
|
||||
relay = SnackbarRelay.VAULT_SETTINGS_RELAY,
|
||||
)
|
||||
mutableSnackbarSharedFlow.tryEmit(expectedSnackbarData)
|
||||
assertEquals(VaultSettingsEvent.ShowSnackbar(expectedSnackbarData), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.manager.snackbar
|
||||
|
||||
import app.cash.turbine.test
|
||||
import app.cash.turbine.turbineScope
|
||||
import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@ -10,93 +11,72 @@ 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)
|
||||
private val relayManager: SnackbarRelayManager = SnackbarRelayManagerImpl(
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
|
||||
consumer.test {
|
||||
@Test
|
||||
fun `when relay is completed successfully when consumer registers first and event is sent`() =
|
||||
runTest {
|
||||
val relay = SnackbarRelay.LOGINS_IMPORTED
|
||||
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||
|
||||
relayManager.getSnackbarDataFlow(relay).test {
|
||||
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||
assertEquals(
|
||||
expectedData,
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(expectedData, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Relay is completed successfully when consumer registers second and event is sent`() =
|
||||
fun `when relay is completed successfully when consumer registers second and event is sent`() =
|
||||
runTest {
|
||||
val relayManager = SnackbarRelayManagerImpl()
|
||||
val relay = SnackbarRelay.MY_VAULT_RELAY
|
||||
val relay = SnackbarRelay.LOGINS_IMPORTED
|
||||
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||
// producer code
|
||||
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||
relayManager.getSnackbarDataFlow(relay).test {
|
||||
assertEquals(
|
||||
expectedData,
|
||||
awaitItem(),
|
||||
)
|
||||
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(),
|
||||
)
|
||||
}
|
||||
fun `when relay is specified by producer only send data to that relay`() = runTest {
|
||||
val relay1 = SnackbarRelay.LOGINS_IMPORTED
|
||||
val relay2 = SnackbarRelay.SEND_DELETED
|
||||
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`() =
|
||||
fun `when multiple consumers are registered to the same relay, send data to last consumers`() =
|
||||
runTest {
|
||||
val relayManager = SnackbarRelayManagerImpl()
|
||||
val relay = SnackbarRelay.MY_VAULT_RELAY
|
||||
val relay = SnackbarRelay.LOGINS_IMPORTED
|
||||
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||
turbineScope {
|
||||
val consumer1 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
|
||||
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||
assertEquals(
|
||||
expectedData,
|
||||
consumer1.awaitItem(),
|
||||
)
|
||||
val consumer2 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
|
||||
assertEquals(
|
||||
expectedData,
|
||||
consumer2.awaitItem(),
|
||||
)
|
||||
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||
assertEquals(expectedData, consumer2.awaitItem())
|
||||
consumer1.expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `When multiple consumers are registered to the same relay, and one is completed before the other the second consumer registers should not receive any emissions`() =
|
||||
fun `when multiple consumers are registered 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 relay = SnackbarRelay.MY_VAULT_RELAY
|
||||
val relay = SnackbarRelay.LOGINS_IMPORTED
|
||||
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||
turbineScope {
|
||||
val consumer1 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
|
||||
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||
assertEquals(
|
||||
expectedData,
|
||||
consumer1.awaitItem(),
|
||||
)
|
||||
assertEquals(expectedData, consumer1.awaitItem())
|
||||
consumer1.cancel()
|
||||
val consumer2 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
|
||||
consumer2.expectNoEvents()
|
||||
@ -105,21 +85,16 @@ class SnackbarRelayManagerTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `When multiple consumers register to the same relay, and clearRelayBuffer is called, the second consumer should not receive any emissions`() =
|
||||
fun `when multiple consumers are registered to the same relay, and the last one is cancelled, the other most recent consumer should receive the emissions`() =
|
||||
runTest {
|
||||
val relayManager = SnackbarRelayManagerImpl()
|
||||
val relay = SnackbarRelay.MY_VAULT_RELAY
|
||||
val relay = SnackbarRelay.LOGINS_IMPORTED
|
||||
val expectedData = BitwardenSnackbarData(message = "Test message".asText())
|
||||
turbineScope {
|
||||
val consumer1 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
|
||||
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||
assertEquals(
|
||||
expectedData,
|
||||
consumer1.awaitItem(),
|
||||
)
|
||||
relayManager.clearRelayBuffer(relay)
|
||||
val consumer2 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
|
||||
consumer2.expectNoEvents()
|
||||
consumer2.cancel()
|
||||
relayManager.sendSnackbarData(data = expectedData, relay = relay)
|
||||
assertEquals(expectedData, consumer1.awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,6 @@ import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.util.advanceTimeByAndRunCurrent
|
||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||
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.just
|
||||
import io.mockk.mockk
|
||||
@ -492,5 +491,4 @@ private val DEFAULT_STATE = ImportLoginsState(
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = "vault.bitwarden.com",
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.importlogins
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
@ -48,10 +47,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockkStatic(
|
||||
SavedStateHandle::toImportLoginsArgs,
|
||||
Uri::parse,
|
||||
)
|
||||
mockkStatic(Uri::parse)
|
||||
every { Uri.parse(Environment.Us.environmentUrlData.base) } returns mockk {
|
||||
every { host } returns DEFAULT_VAULT_URL
|
||||
}
|
||||
@ -59,10 +55,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(
|
||||
SavedStateHandle::toImportLoginsArgs,
|
||||
Uri::parse,
|
||||
)
|
||||
unmockkStatic(Uri::parse)
|
||||
}
|
||||
|
||||
private val snackbarRelayManager: SnackbarRelayManagerImpl = mockk {
|
||||
@ -88,7 +81,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -104,7 +96,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -125,7 +116,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@ -136,7 +126,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@ -160,7 +149,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
stateFlow.awaitItem(),
|
||||
)
|
||||
@ -171,7 +159,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
stateFlow.awaitItem(),
|
||||
)
|
||||
@ -201,7 +188,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@ -212,7 +198,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.ImportStepOne,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@ -253,7 +238,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.ImportStepOne,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -269,7 +253,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.ImportStepTwo,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -285,7 +268,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.ImportStepThree,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -305,7 +287,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -326,7 +307,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@ -354,7 +334,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@ -374,7 +353,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = true,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -404,7 +382,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@ -417,7 +394,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@ -435,7 +411,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -458,7 +433,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
@ -484,7 +458,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
stateFlow.awaitItem(),
|
||||
)
|
||||
@ -494,7 +467,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = true,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
stateFlow.awaitItem(),
|
||||
)
|
||||
@ -505,7 +477,6 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
),
|
||||
stateFlow.awaitItem(),
|
||||
)
|
||||
@ -519,17 +490,12 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
verify {
|
||||
snackbarRelayManager.sendSnackbarData(
|
||||
data = expectedSnackbarData,
|
||||
relay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
relay = SnackbarRelay.LOGINS_IMPORTED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
snackbarRelay: SnackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
): ImportLoginsViewModel = ImportLoginsViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
every { toImportLoginsArgs() } returns ImportLoginsArgs(snackBarRelay = snackbarRelay)
|
||||
},
|
||||
private fun createViewModel(): ImportLoginsViewModel = ImportLoginsViewModel(
|
||||
vaultRepository = vaultRepository,
|
||||
firstTimeActionManager = firstTimeActionManager,
|
||||
environmentRepository = environmentRepository,
|
||||
@ -544,5 +510,4 @@ private val DEFAULT_STATE = ImportLoginsState(
|
||||
viewState = ImportLoginsState.ViewState.InitialContent,
|
||||
showBottomSheet = false,
|
||||
currentWebVaultUrl = DEFAULT_VAULT_URL,
|
||||
snackbarRelay = SnackbarRelay.MY_VAULT_RELAY,
|
||||
)
|
||||
|
||||
@ -38,7 +38,6 @@ 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.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.review.AppReviewManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertLogoutConfirmationDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertRemovalConfirmationDialogIsDisplayed
|
||||
@ -118,10 +117,7 @@ class VaultScreenTest : BitwardenComposeTest() {
|
||||
onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true },
|
||||
onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = true },
|
||||
onNavigateToSearchVault = { onNavigateToSearchScreen = true },
|
||||
onNavigateToImportLogins = {
|
||||
onNavigateToImportLoginsCalled = true
|
||||
assertEquals(SnackbarRelay.MY_VAULT_RELAY, it)
|
||||
},
|
||||
onNavigateToImportLogins = { onNavigateToImportLoginsCalled = true },
|
||||
onNavigateToAddFolderScreen = { folderName ->
|
||||
onNavigateToAddFolderCalled = true
|
||||
onNavigateToAddFolderParentFolderName = folderName
|
||||
|
||||
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.vault
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.data.repository.util.baseIconUrl
|
||||
import com.bitwarden.network.model.OrganizationType
|
||||
@ -63,7 +64,6 @@ import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@ -83,11 +83,11 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
private val mutableSnackbarDataFlow = MutableStateFlow<BitwardenSnackbarData?>(null)
|
||||
private val mutableSnackbarDataFlow = bufferedMutableSharedFlow<BitwardenSnackbarData>()
|
||||
private val snackbarRelayManager: SnackbarRelayManager = mockk {
|
||||
every { getSnackbarDataFlow(SnackbarRelay.MY_VAULT_RELAY) } returns mutableSnackbarDataFlow
|
||||
.filterNotNull()
|
||||
every { clearRelayBuffer(SnackbarRelay.MY_VAULT_RELAY) } just runs
|
||||
every {
|
||||
getSnackbarDataFlow(SnackbarRelay.LOGINS_IMPORTED)
|
||||
} returns mutableSnackbarDataFlow
|
||||
}
|
||||
|
||||
private val clipboardManager: BitwardenClipboardManager = mockk {
|
||||
@ -2050,8 +2050,8 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
fun `when SnackbarRelay flow updates, snackbar is shown`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
val expectedSnackbarData = BitwardenSnackbarData(message = "test message".asText())
|
||||
mutableSnackbarDataFlow.update { expectedSnackbarData }
|
||||
viewModel.eventFlow.test {
|
||||
mutableSnackbarDataFlow.tryEmit(expectedSnackbarData)
|
||||
assertEquals(VaultEvent.ShowSnackbar(expectedSnackbarData), awaitItem())
|
||||
}
|
||||
}
|
||||
@ -2068,9 +2068,6 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
},
|
||||
),
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
snackbarRelayManager.clearRelayBuffer(SnackbarRelay.MY_VAULT_RELAY)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.bitwarden.core.data.repository.util
|
||||
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
/**
|
||||
* Creates a [MutableSharedFlow] with a buffer of [Int.MAX_VALUE] and the given [replay] count.
|
||||
@ -12,3 +13,17 @@ fun <T> bufferedMutableSharedFlow(
|
||||
replay = replay,
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
|
||||
/**
|
||||
* Emits a [value] to this shared flow, suspending until there is at least one subscriber.
|
||||
*/
|
||||
suspend fun <T> MutableSharedFlow<T>.emitWhenSubscribedTo(value: T) {
|
||||
// We have subscribers, so emit now.
|
||||
if (subscriptionCount.value > 0) {
|
||||
emit(value = value)
|
||||
return
|
||||
}
|
||||
// We are going to wait until there is at least one subscriber, then emit.
|
||||
subscriptionCount.first { it > 0 }
|
||||
emit(value = value)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user