mirror of
https://github.com/bitwarden/android.git
synced 2026-08-28 22:45:25 -05:00
PM-22551: Update toasts to snackbars for Sends (#5339)
This commit is contained in:
+1
-1
@@ -84,6 +84,6 @@ data class BitwardenSnackbarData(
|
||||
fun rememberBitwardenSnackbarHostState(
|
||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||
scope: CoroutineScope = rememberCoroutineScope(),
|
||||
) = remember {
|
||||
): BitwardenSnackbarHostState = remember {
|
||||
BitwardenSnackbarHostState(snackbarHostState = snackbarHostState, scope = scope)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import com.x8bit.bitwarden.ui.platform.components.content.BitwardenLoadingConten
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
|
||||
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.composition.LocalAppResumeStateManager
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.handlers.SearchHandlers
|
||||
@@ -72,6 +74,7 @@ fun SearchScreen(
|
||||
)
|
||||
}
|
||||
|
||||
val snackbarHostState = rememberBitwardenSnackbarHostState()
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
SearchEvent.NavigateBack -> onNavigateBack()
|
||||
@@ -111,6 +114,7 @@ fun SearchScreen(
|
||||
|
||||
is SearchEvent.NavigateToUrl -> intentManager.launchUri(event.url.toUri())
|
||||
is SearchEvent.ShowShareSheet -> intentManager.shareText(event.content)
|
||||
is SearchEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||
is SearchEvent.ShowToast -> {
|
||||
Toast
|
||||
.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT)
|
||||
@@ -159,6 +163,7 @@ fun SearchScreen(
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = { BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) },
|
||||
modifier = Modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.bitwarden.data.repository.util.baseIconUrl
|
||||
import com.bitwarden.data.repository.util.baseWebSendUrl
|
||||
import com.bitwarden.network.model.PolicyTypeJson
|
||||
import com.bitwarden.send.SendType
|
||||
import com.bitwarden.ui.platform.base.BackgroundEvent
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
@@ -39,12 +40,15 @@ import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.AutofillSelectionOption
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.filterAndOrganize
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.toSearchTypeData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.updateWithAdditionalDataIfNecessary
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendItemType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||
@@ -81,6 +85,7 @@ class SearchViewModel @Inject constructor(
|
||||
private val accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||
private val autofillSelectionManager: AutofillSelectionManager,
|
||||
private val organizationEventManager: OrganizationEventManager,
|
||||
private val snackbarRelayManager: SnackbarRelayManager,
|
||||
private val vaultRepo: VaultRepository,
|
||||
private val authRepo: AuthRepository,
|
||||
environmentRepo: EnvironmentRepository,
|
||||
@@ -135,6 +140,11 @@ class SearchViewModel @Inject constructor(
|
||||
.map { SearchAction.Internal.VaultDataReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
snackbarRelayManager
|
||||
.getSnackbarDataFlow(SnackbarRelay.SEND_DELETED, SnackbarRelay.SEND_UPDATED)
|
||||
.map { SearchAction.Internal.SnackbarDataReceived(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: SearchAction) {
|
||||
@@ -473,6 +483,8 @@ class SearchViewModel @Inject constructor(
|
||||
handleRemovePasswordSendResultReceive(action)
|
||||
}
|
||||
|
||||
is SearchAction.Internal.SnackbarDataReceived -> handleSnackbarDataReceived(action)
|
||||
|
||||
is SearchAction.Internal.UpdateCipherResultReceive -> {
|
||||
handleUpdateCipherResultReceive(action)
|
||||
}
|
||||
@@ -554,6 +566,10 @@ class SearchViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSnackbarDataReceived(action: SearchAction.Internal.SnackbarDataReceived) {
|
||||
sendEvent(SearchEvent.ShowSnackbar(action.data))
|
||||
}
|
||||
|
||||
private fun handleUpdateCipherResultReceive(
|
||||
action: SearchAction.Internal.UpdateCipherResultReceive,
|
||||
) {
|
||||
@@ -1184,6 +1200,13 @@ sealed class SearchAction {
|
||||
val result: RemovePasswordSendResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that snackbar data has been received.
|
||||
*/
|
||||
data class SnackbarDataReceived(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a result for updating a cipher during the autofill-and-save process.
|
||||
*/
|
||||
@@ -1265,6 +1288,13 @@ sealed class SearchEvent {
|
||||
val content: String,
|
||||
) : SearchEvent()
|
||||
|
||||
/**
|
||||
* Show a snackbar to the user.
|
||||
*/
|
||||
data class ShowSnackbar(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : SearchEvent(), BackgroundEvent
|
||||
|
||||
/**
|
||||
* Show a toast with the given [message].
|
||||
*/
|
||||
|
||||
@@ -11,4 +11,5 @@ import kotlinx.serialization.Serializable
|
||||
enum class SnackbarRelay {
|
||||
LOGINS_IMPORTED,
|
||||
SEND_DELETED,
|
||||
SEND_UPDATED,
|
||||
}
|
||||
|
||||
+5
-2
@@ -16,7 +16,10 @@ interface SnackbarRelayManager {
|
||||
|
||||
/**
|
||||
* Called from a consumer to receive snackbar data from a producer, the consumer must specify
|
||||
* the [relay] to receive the data from.
|
||||
* the [relay] or [relays] to receive the data from.
|
||||
*/
|
||||
fun getSnackbarDataFlow(relay: SnackbarRelay): Flow<BitwardenSnackbarData>
|
||||
fun getSnackbarDataFlow(
|
||||
relay: SnackbarRelay,
|
||||
vararg relays: SnackbarRelay,
|
||||
): Flow<BitwardenSnackbarData>
|
||||
}
|
||||
|
||||
+9
-3
@@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -34,9 +35,14 @@ class SnackbarRelayManagerImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSnackbarDataFlow(relay: SnackbarRelay): Flow<BitwardenSnackbarData> =
|
||||
snackbarSharedFlow
|
||||
.generateFlowFor(relay = relay)
|
||||
override fun getSnackbarDataFlow(
|
||||
relay: SnackbarRelay,
|
||||
vararg relays: SnackbarRelay,
|
||||
): Flow<BitwardenSnackbarData> =
|
||||
merge(
|
||||
snackbarSharedFlow.generateFlowFor(relay = relay),
|
||||
*relays.map { snackbarSharedFlow.generateFlowFor(relay = it) }.toTypedArray(),
|
||||
)
|
||||
.map { it.data }
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialo
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.row.BitwardenBasicDialogRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.rememberBitwardenPullToRefreshState
|
||||
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.composition.LocalAppResumeStateManager
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
@@ -82,6 +84,7 @@ fun SendScreen(
|
||||
AppResumeScreenData.SendScreen
|
||||
}
|
||||
|
||||
val snackbarHostState = rememberBitwardenSnackbarHostState()
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is SendEvent.NavigateToSearch -> onNavigateToSearchSend(SearchType.Sends.All)
|
||||
@@ -116,6 +119,8 @@ fun SendScreen(
|
||||
intentManager.shareText(event.url)
|
||||
}
|
||||
|
||||
is SendEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||
|
||||
is SendEvent.ShowToast -> {
|
||||
Toast
|
||||
.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT)
|
||||
@@ -197,6 +202,7 @@ fun SendScreen(
|
||||
}
|
||||
},
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
snackbarHost = { BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) },
|
||||
) {
|
||||
val modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.data.repository.util.baseWebSendUrl
|
||||
import com.bitwarden.network.model.PolicyTypeJson
|
||||
import com.bitwarden.ui.platform.base.BackgroundEvent
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
@@ -23,6 +24,9 @@ import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||
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.tools.feature.send.model.SendItemType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemScreen
|
||||
@@ -49,6 +53,7 @@ class SendViewModel @Inject constructor(
|
||||
authRepo: AuthRepository,
|
||||
settingsRepo: SettingsRepository,
|
||||
policyManager: PolicyManager,
|
||||
snackbarRelayManager: SnackbarRelayManager,
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
private val environmentRepo: EnvironmentRepository,
|
||||
private val vaultRepo: VaultRepository,
|
||||
@@ -89,6 +94,11 @@ class SendViewModel @Inject constructor(
|
||||
.map { SendAction.Internal.UserStateReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
snackbarRelayManager
|
||||
.getSnackbarDataFlow(SnackbarRelay.SEND_DELETED, SnackbarRelay.SEND_UPDATED)
|
||||
.map { SendAction.Internal.SnackbarDataReceived(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: SendAction): Unit = when (action) {
|
||||
@@ -132,6 +142,7 @@ class SendViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
is SendAction.Internal.UserStateReceive -> handleUserStateReceive(action)
|
||||
is SendAction.Internal.SnackbarDataReceived -> handleSnackbarDataReceived(action)
|
||||
}
|
||||
|
||||
private fun handleInternetConnectionErrorReceived() {
|
||||
@@ -152,6 +163,10 @@ class SendViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSnackbarDataReceived(action: SendAction.Internal.SnackbarDataReceived) {
|
||||
sendEvent(SendEvent.ShowSnackbar(action.data))
|
||||
}
|
||||
|
||||
private fun handlePullToRefreshEnableReceive(
|
||||
action: SendAction.Internal.PullToRefreshEnableReceive,
|
||||
) {
|
||||
@@ -701,6 +716,13 @@ sealed class SendAction {
|
||||
val result: RemovePasswordSendResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that snackbar data has been received from a relay to be displayed.
|
||||
*/
|
||||
data class SnackbarDataReceived(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates what the current [userState] is.
|
||||
*/
|
||||
@@ -781,6 +803,13 @@ sealed class SendEvent {
|
||||
*/
|
||||
data class ShowShareSheet(val url: String) : SendEvent()
|
||||
|
||||
/**
|
||||
* Show a snackbar to the user.
|
||||
*/
|
||||
data class ShowSnackbar(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : SendEvent(), BackgroundEvent
|
||||
|
||||
/**
|
||||
* Show a toast to the user.
|
||||
*/
|
||||
|
||||
+5
-8
@@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.ui.tools.feature.send.addedit
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -11,7 +10,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
@@ -31,6 +29,8 @@ import com.x8bit.bitwarden.ui.platform.components.content.BitwardenLoadingConten
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
|
||||
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.composition.LocalExitManager
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalPermissionsManager
|
||||
@@ -57,15 +57,13 @@ fun AddEditSendScreen(
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val addSendHandlers = remember(viewModel) { AddEditSendHandlers.create(viewModel) }
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
|
||||
val fileChooserLauncher = intentManager.getActivityResultLauncher { activityResult ->
|
||||
intentManager.getFileDataFromActivityResult(activityResult)?.let {
|
||||
addSendHandlers.onFileChoose(it)
|
||||
}
|
||||
}
|
||||
|
||||
val snackbarHostState = rememberBitwardenSnackbarHostState()
|
||||
BackHandler(
|
||||
onBack = remember(viewModel) {
|
||||
{ viewModel.trySendAction(AddEditSendAction.CloseClick) }
|
||||
@@ -89,9 +87,7 @@ fun AddEditSendScreen(
|
||||
intentManager.shareText(event.message)
|
||||
}
|
||||
|
||||
is AddEditSendEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message(resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is AddEditSendEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +162,7 @@ fun AddEditSendScreen(
|
||||
},
|
||||
)
|
||||
},
|
||||
snackbarHost = { BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) },
|
||||
) {
|
||||
val modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
+21
-4
@@ -27,7 +27,10 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
|
||||
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 com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.model.AddEditSendType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.shouldFinishOnComplete
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toSendName
|
||||
@@ -70,6 +73,7 @@ class AddEditSendViewModel @Inject constructor(
|
||||
private val vaultRepo: VaultRepository,
|
||||
private val policyManager: PolicyManager,
|
||||
private val networkConnectionManager: NetworkConnectionManager,
|
||||
private val snackbarRelayManager: SnackbarRelayManager,
|
||||
) : BaseViewModel<AddEditSendState, AddEditSendEvent, AddEditSendAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE] ?: run {
|
||||
@@ -250,6 +254,10 @@ class AddEditSendViewModel @Inject constructor(
|
||||
message = result.sendView.toSendUrl(state.baseWebSendUrl),
|
||||
),
|
||||
)
|
||||
snackbarRelayManager.sendSnackbarData(
|
||||
data = BitwardenSnackbarData(message = R.string.send_updated.asText()),
|
||||
relay = SnackbarRelay.SEND_UPDATED,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +281,10 @@ class AddEditSendViewModel @Inject constructor(
|
||||
is DeleteSendResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
navigateBack(isDeleted = true)
|
||||
sendEvent(AddEditSendEvent.ShowToast(message = R.string.send_deleted.asText()))
|
||||
snackbarRelayManager.sendSnackbarData(
|
||||
data = BitwardenSnackbarData(message = R.string.send_deleted.asText()),
|
||||
relay = SnackbarRelay.SEND_DELETED,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,7 +312,11 @@ class AddEditSendViewModel @Inject constructor(
|
||||
updateCommonContent { it.copy(hasPassword = false) }
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
sendEvent(
|
||||
AddEditSendEvent.ShowToast(message = R.string.send_password_removed.asText()),
|
||||
AddEditSendEvent.ShowSnackbar(
|
||||
data = BitwardenSnackbarData(
|
||||
message = R.string.send_password_removed.asText(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -845,9 +860,11 @@ sealed class AddEditSendEvent {
|
||||
) : BackgroundEvent, AddEditSendEvent()
|
||||
|
||||
/**
|
||||
* Show Toast.
|
||||
* Show a snackbar.
|
||||
*/
|
||||
data class ShowToast(val message: Text) : AddEditSendEvent()
|
||||
data class ShowSnackbar(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : AddEditSendEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-4
@@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.ui.tools.feature.send.viewsend
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
@@ -68,6 +67,8 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenExpandingHeader
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
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.stepper.BitwardenStepper
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
@@ -89,6 +90,7 @@ fun ViewSendScreen(
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
val snackbarHostState = rememberBitwardenSnackbarHostState()
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is ViewSendEvent.NavigateBack -> onNavigateBack()
|
||||
@@ -106,9 +108,7 @@ fun ViewSendScreen(
|
||||
intentManager.shareText(text = event.text(resources).toString())
|
||||
}
|
||||
|
||||
is ViewSendEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message(resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is ViewSendEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ fun ViewSendScreen(
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = { BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) },
|
||||
) {
|
||||
ViewSendScreenContent(
|
||||
state = state,
|
||||
|
||||
+28
-4
@@ -16,6 +16,9 @@ import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardMan
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
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.tools.feature.send.model.SendItemType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.viewsend.util.toViewSendViewStateContent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -37,6 +40,7 @@ private const val KEY_STATE = "state"
|
||||
@HiltViewModel
|
||||
class ViewSendViewModel @Inject constructor(
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
private val snackbarRelayManager: SnackbarRelayManager,
|
||||
private val clock: Clock,
|
||||
private val vaultRepository: VaultRepository,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
@@ -60,6 +64,11 @@ class ViewSendViewModel @Inject constructor(
|
||||
.map { ViewSendAction.Internal.SendDataReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
snackbarRelayManager
|
||||
.getSnackbarDataFlow(SnackbarRelay.SEND_UPDATED)
|
||||
.map { ViewSendAction.Internal.SnackbarDataReceived(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: ViewSendAction) {
|
||||
@@ -79,6 +88,7 @@ class ViewSendViewModel @Inject constructor(
|
||||
when (action) {
|
||||
is ViewSendAction.Internal.SendDataReceive -> handleSendDataReceive(action)
|
||||
is ViewSendAction.Internal.DeleteResultReceive -> handleDeleteResultReceive(action)
|
||||
is ViewSendAction.Internal.SnackbarDataReceived -> handleSnackbarDataReceived(action)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,12 +144,19 @@ class ViewSendViewModel @Inject constructor(
|
||||
|
||||
is DeleteSendResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
sendEvent(ViewSendEvent.ShowToast(message = R.string.send_deleted.asText()))
|
||||
snackbarRelayManager.sendSnackbarData(
|
||||
data = BitwardenSnackbarData(message = R.string.send_deleted.asText()),
|
||||
relay = SnackbarRelay.SEND_DELETED,
|
||||
)
|
||||
sendEvent(ViewSendEvent.NavigateBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSnackbarDataReceived(action: ViewSendAction.Internal.SnackbarDataReceived) {
|
||||
sendEvent(ViewSendEvent.ShowSnackbar(action.data))
|
||||
}
|
||||
|
||||
private fun handleSendDataReceive(action: ViewSendAction.Internal.SendDataReceive) {
|
||||
when (val dataState = action.sendDataState) {
|
||||
is DataState.Error -> sendErrorReceive(dataState = dataState)
|
||||
@@ -340,10 +357,10 @@ sealed class ViewSendEvent {
|
||||
) : ViewSendEvent()
|
||||
|
||||
/**
|
||||
* Shows the [message] via a toast.
|
||||
* Show a snackbar to the user.
|
||||
*/
|
||||
data class ShowToast(
|
||||
val message: Text,
|
||||
data class ShowSnackbar(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : ViewSendEvent(), BackgroundEvent
|
||||
}
|
||||
|
||||
@@ -399,5 +416,12 @@ sealed class ViewSendAction {
|
||||
* Indicates that the send item data has been received.
|
||||
*/
|
||||
data class SendDataReceive(val sendDataState: DataState<SendView?>) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that snackbar data has been received.
|
||||
*/
|
||||
data class SnackbarDataReceived(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -45,6 +45,9 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialo
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.BitwardenPullToRefreshState
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.rememberBitwardenPullToRefreshState
|
||||
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.BitwardenSnackbarHostState
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.rememberBitwardenSnackbarHostState
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalCredentialProviderCompletionManager
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager
|
||||
@@ -105,6 +108,7 @@ fun VaultItemListingScreen(
|
||||
{ viewModel.trySendAction(VaultItemListingsAction.RefreshPull) }
|
||||
},
|
||||
)
|
||||
val snackbarHostState = rememberBitwardenSnackbarHostState()
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is VaultItemListingEvent.NavigateBack -> onNavigateBack()
|
||||
@@ -217,6 +221,8 @@ fun VaultItemListingScreen(
|
||||
is VaultItemListingEvent.NavigateToAddFolder -> {
|
||||
onNavigateToAddFolder(event.parentFolderName)
|
||||
}
|
||||
|
||||
is VaultItemListingEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +331,7 @@ fun VaultItemListingScreen(
|
||||
BackHandler(onBack = vaultItemListingHandlers.backClick)
|
||||
VaultItemListingScaffold(
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
vaultItemListingHandlers = vaultItemListingHandlers,
|
||||
)
|
||||
@@ -478,6 +485,7 @@ private fun VaultItemListingDialogs(
|
||||
private fun VaultItemListingScaffold(
|
||||
state: VaultItemListingState,
|
||||
pullToRefreshState: BitwardenPullToRefreshState,
|
||||
snackbarHostState: BitwardenSnackbarHostState,
|
||||
vaultItemListingHandlers: VaultItemListingHandlers,
|
||||
) {
|
||||
var isAccountMenuVisible by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -553,6 +561,7 @@ private fun VaultItemListingScaffold(
|
||||
)
|
||||
},
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
snackbarHost = { BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) },
|
||||
) {
|
||||
when (state.viewState) {
|
||||
is VaultItemListingState.ViewState.Content -> {
|
||||
|
||||
+34
@@ -72,9 +72,12 @@ import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
||||
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.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.SearchTypeData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.filterAndOrganize
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendItemType
|
||||
@@ -132,6 +135,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
private val bitwardenCredentialManager: BitwardenCredentialManager,
|
||||
private val organizationEventManager: OrganizationEventManager,
|
||||
private val networkConnectionManager: NetworkConnectionManager,
|
||||
private val snackbarRelayManager: SnackbarRelayManager,
|
||||
) : BaseViewModel<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
|
||||
initialState = run {
|
||||
val userState = requireNotNull(authRepository.userStateFlow.value)
|
||||
@@ -189,6 +193,12 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
snackbarRelayManager
|
||||
.getSnackbarDataFlow(SnackbarRelay.SEND_DELETED, SnackbarRelay.SEND_UPDATED)
|
||||
.map { VaultItemListingsAction.Internal.SnackbarDataReceived(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
specialCircumstanceManager.specialCircumstance
|
||||
?.toCreateCredentialRequestOrNull()
|
||||
?.let { request ->
|
||||
@@ -1403,6 +1413,10 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
is VaultItemListingsAction.Internal.GetCredentialEntriesResultReceive -> {
|
||||
handleGetCredentialEntriesResultReceive(action)
|
||||
}
|
||||
|
||||
is VaultItemListingsAction.Internal.SnackbarDataReceived -> {
|
||||
handleSnackbarDataReceived(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2038,6 +2052,12 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSnackbarDataReceived(
|
||||
action: VaultItemListingsAction.Internal.SnackbarDataReceived,
|
||||
) {
|
||||
sendEvent(VaultItemListingEvent.ShowSnackbar(action.data))
|
||||
}
|
||||
|
||||
private fun updateStateWithVaultData(vaultData: VaultData, clearDialogState: Boolean) {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
@@ -2769,6 +2789,13 @@ sealed class VaultItemListingEvent {
|
||||
*/
|
||||
data class ShowToast(val text: Text) : VaultItemListingEvent()
|
||||
|
||||
/**
|
||||
* Show a snackbar to the user.
|
||||
*/
|
||||
data class ShowSnackbar(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : VaultItemListingEvent(), BackgroundEvent
|
||||
|
||||
/**
|
||||
* Complete the current FIDO 2 credential registration process.
|
||||
*
|
||||
@@ -3138,6 +3165,13 @@ sealed class VaultItemListingsAction {
|
||||
val userId: String,
|
||||
val result: Result<List<CredentialEntry>>,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that snackbar data has been received.
|
||||
*/
|
||||
data class SnackbarDataReceived(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -466,6 +466,7 @@ Scanning will happen automatically.</string>
|
||||
<string name="add_text_send">New text Send</string>
|
||||
<string name="are_you_sure_delete_send">Are you sure you want to delete this Send?</string>
|
||||
<string name="send_deleted">Send deleted</string>
|
||||
<string name="send_updated">Send updated</string>
|
||||
<string name="one_day">1 day</string>
|
||||
<string name="two_days">2 days</string>
|
||||
<string name="three_days">3 days</string>
|
||||
|
||||
+11
@@ -26,6 +26,7 @@ import com.bitwarden.ui.util.isProgressBar
|
||||
import com.bitwarden.vault.CipherType
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.AppResumeStateManager
|
||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.AutofillSelectionOption
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.createMockDisplayItemForCipher
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.createMockDisplayItemForSend
|
||||
@@ -151,6 +152,16 @@ class SearchScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowSnackbar event should display the snackbar`() {
|
||||
val message = "message"
|
||||
val data = BitwardenSnackbarData(message = message.asText())
|
||||
mutableEventFlow.tryEmit(SearchEvent.ShowSnackbar(data = data))
|
||||
composeTestRule
|
||||
.onNodeWithText(text = message)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ShowShareSheet should call onNavigateBack`() {
|
||||
val sendUrl = "www.test.com"
|
||||
|
||||
+22
@@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import app.cash.turbine.turbineScope
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.network.model.PolicyTypeJson
|
||||
@@ -50,10 +51,12 @@ import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.createMockDisplayItemForCipher
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.filterAndOrganize
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
@@ -69,6 +72,7 @@ import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
@@ -129,6 +133,13 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
private val organizationEventManager = mockk<OrganizationEventManager> {
|
||||
every { trackEvent(event = any()) } just runs
|
||||
}
|
||||
private val mutableSnackbarDataFlow: MutableSharedFlow<BitwardenSnackbarData> =
|
||||
bufferedMutableSharedFlow()
|
||||
private val snackbarRelayManager: SnackbarRelayManager = mockk {
|
||||
every {
|
||||
getSnackbarDataFlow(relay = any(), relays = anyVararg())
|
||||
} returns mutableSnackbarDataFlow
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
@@ -1554,6 +1565,16 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
assertTrue(viewModel.stateFlow.value.isIconLoadingDisabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SnackbarDataReceive should update emit ShowSnackbar`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
val snackbarData = BitwardenSnackbarData(message = "Test".asText())
|
||||
viewModel.eventFlow.test {
|
||||
mutableSnackbarDataFlow.tryEmit(snackbarData)
|
||||
assertEquals(SearchEvent.ShowSnackbar(data = snackbarData), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun createViewModel(
|
||||
initialState: SearchState? = null,
|
||||
@@ -1599,6 +1620,7 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
accessibilitySelectionManager = accessibilitySelectionManager,
|
||||
autofillSelectionManager = autofillSelectionManager,
|
||||
organizationEventManager = organizationEventManager,
|
||||
snackbarRelayManager = snackbarRelayManager,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.bitwarden.ui.util.isProgressBar
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.AppResumeStateManager
|
||||
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.tools.feature.send.addedit.AddEditSendRoute
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.ModeType
|
||||
@@ -139,6 +140,16 @@ class SendScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowSnackbar event should display the snackbar`() {
|
||||
val message = "message"
|
||||
val data = BitwardenSnackbarData(message = message.asText())
|
||||
mutableEventFlow.tryEmit(SendEvent.ShowSnackbar(data = data))
|
||||
composeTestRule
|
||||
.onNodeWithText(text = message)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowShareSheet should call shareText on IntentManager`() {
|
||||
val text = "sharable stuff"
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.tools.feature.send
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
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.baseWebSendUrl
|
||||
import com.bitwarden.network.model.PolicyTypeJson
|
||||
@@ -23,6 +24,8 @@ import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toViewState
|
||||
import io.mockk.coEvery
|
||||
@@ -34,6 +37,7 @@ import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
@@ -72,6 +76,13 @@ class SendViewModelTest : BaseViewModelTest() {
|
||||
private val networkConnectionManager: NetworkConnectionManager = mockk {
|
||||
every { isNetworkConnected } returns true
|
||||
}
|
||||
private val mutableSnackbarDataFlow: MutableSharedFlow<BitwardenSnackbarData> =
|
||||
bufferedMutableSharedFlow()
|
||||
private val snackbarRelayManager: SnackbarRelayManager = mockk {
|
||||
every {
|
||||
getSnackbarDataFlow(relay = any(), relays = anyVararg())
|
||||
} returns mutableSnackbarDataFlow
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
@@ -629,6 +640,16 @@ class SendViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SnackbarDataReceive should update emit ShowSnackbar`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
val snackbarData = BitwardenSnackbarData(message = "Test".asText())
|
||||
viewModel.eventFlow.test {
|
||||
mutableSnackbarDataFlow.tryEmit(snackbarData)
|
||||
assertEquals(SendEvent.ShowSnackbar(data = snackbarData), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun createViewModel(
|
||||
state: SendState? = null,
|
||||
@@ -649,6 +670,7 @@ class SendViewModelTest : BaseViewModelTest() {
|
||||
vaultRepo = vaultRepository,
|
||||
policyManager = policyManager,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
snackbarRelayManager = snackbarRelayManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -27,6 +27,7 @@ import com.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.bitwarden.ui.util.isEditableText
|
||||
import com.bitwarden.ui.util.isProgressBar
|
||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||
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.permissions.FakePermissionManager
|
||||
@@ -100,6 +101,16 @@ class AddEditSendScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowSnackbar event should display the snackbar`() {
|
||||
val message = "message"
|
||||
val data = BitwardenSnackbarData(message = message.asText())
|
||||
mutableEventFlow.tryEmit(AddEditSendEvent.ShowSnackbar(data = data))
|
||||
composeTestRule
|
||||
.onNodeWithText(text = message)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowShareSheet should call shareText on IntentManager`() {
|
||||
val text = "sharable stuff"
|
||||
|
||||
+31
-14
@@ -24,7 +24,10 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
|
||||
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 com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.model.AddEditSendType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toSendView
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toViewState
|
||||
@@ -81,6 +84,9 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
||||
private val networkConnectionManager = mockk<NetworkConnectionManager> {
|
||||
every { isNetworkConnected } returns true
|
||||
}
|
||||
private val snackbarRelayManager: SnackbarRelayManager = mockk {
|
||||
every { sendSnackbarData(data = any(), relay = any()) } just runs
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
@@ -594,7 +600,11 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AddEditSendAction.RemovePasswordClick)
|
||||
assertEquals(
|
||||
AddEditSendEvent.ShowToast(R.string.send_password_removed.asText()),
|
||||
AddEditSendEvent.ShowSnackbar(
|
||||
data = BitwardenSnackbarData(
|
||||
message = R.string.send_password_removed.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
@@ -649,22 +659,28 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DeleteClick vaultRepository deleteSend Success should show toast`() = runTest {
|
||||
val sendId = "mockId-1"
|
||||
coEvery { vaultRepository.deleteSend(sendId) } returns DeleteSendResult.Success
|
||||
val viewModel = createViewModel(
|
||||
state = DEFAULT_STATE.copy(
|
||||
fun `DeleteClick vaultRepository deleteSend Success should emit NavigateUpToSearchOrRoot`() =
|
||||
runTest {
|
||||
val sendId = "mockId-1"
|
||||
coEvery { vaultRepository.deleteSend(sendId) } returns DeleteSendResult.Success
|
||||
val viewModel = createViewModel(
|
||||
state = DEFAULT_STATE.copy(
|
||||
addEditSendType = AddEditSendType.EditItem(sendItemId = sendId),
|
||||
),
|
||||
addEditSendType = AddEditSendType.EditItem(sendItemId = sendId),
|
||||
),
|
||||
addEditSendType = AddEditSendType.EditItem(sendItemId = sendId),
|
||||
)
|
||||
)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AddEditSendAction.DeleteClick)
|
||||
assertEquals(AddEditSendEvent.NavigateUpToSearchOrRoot, awaitItem())
|
||||
assertEquals(AddEditSendEvent.ShowToast(R.string.send_deleted.asText()), awaitItem())
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AddEditSendAction.DeleteClick)
|
||||
assertEquals(AddEditSendEvent.NavigateUpToSearchOrRoot, awaitItem())
|
||||
}
|
||||
verify(exactly = 1) {
|
||||
snackbarRelayManager.sendSnackbarData(
|
||||
data = BitwardenSnackbarData(message = R.string.send_deleted.asText()),
|
||||
relay = SnackbarRelay.SEND_DELETED,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ShareLinkClick with nonnull sendUrl should launch share sheet`() = runTest {
|
||||
@@ -959,6 +975,7 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
||||
vaultRepo = vaultRepository,
|
||||
policyManager = policyManager,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
snackbarRelayManager = snackbarRelayManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+7
-19
@@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.ui.tools.feature.send.viewsend
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertCountEquals
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
@@ -16,6 +15,7 @@ import com.bitwarden.ui.util.asText
|
||||
import com.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.bitwarden.ui.util.isProgressBar
|
||||
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.tools.feature.send.addedit.AddEditSendRoute
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.ModeType
|
||||
@@ -23,13 +23,10 @@ import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
@@ -52,7 +49,6 @@ class ViewSendScreenTest : BitwardenComposeTest() {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(Toast::class)
|
||||
setContent(
|
||||
intentManager = intentManager,
|
||||
) {
|
||||
@@ -64,11 +60,6 @@ class ViewSendScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
unmockkStatic(Toast::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateBack event should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(ViewSendEvent.NavigateBack)
|
||||
@@ -106,16 +97,13 @@ class ViewSendScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowToast event should call onNavigateToEdit`() {
|
||||
fun `on ShowSnackbar event should display the snackbar`() {
|
||||
val message = "message"
|
||||
val toast = mockk<Toast> {
|
||||
every { show() } just runs
|
||||
}
|
||||
every { Toast.makeText(any(), message, Toast.LENGTH_SHORT) } returns toast
|
||||
mutableEventFlow.tryEmit(ViewSendEvent.ShowToast(message = message.asText()))
|
||||
verify(exactly = 1) {
|
||||
toast.show()
|
||||
}
|
||||
val data = BitwardenSnackbarData(message = message.asText())
|
||||
mutableEventFlow.tryEmit(ViewSendEvent.ShowSnackbar(data = data))
|
||||
composeTestRule
|
||||
.onNodeWithText(text = message)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+32
-7
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.tools.feature.send.viewsend
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
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.send.SendView
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
@@ -14,6 +15,9 @@ import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
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.tools.feature.send.model.SendItemType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.viewsend.util.toViewSendViewStateContent
|
||||
import io.mockk.coEvery
|
||||
@@ -24,6 +28,7 @@ import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
@@ -46,6 +51,14 @@ class ViewSendViewModelTest : BaseViewModelTest() {
|
||||
private val environmentRepository = mockk<EnvironmentRepository> {
|
||||
every { environment } returns Environment.Us
|
||||
}
|
||||
private val mutableSnackbarDataFlow: MutableSharedFlow<BitwardenSnackbarData> =
|
||||
bufferedMutableSharedFlow()
|
||||
private val snackbarRelayManager: SnackbarRelayManager = mockk {
|
||||
every { sendSnackbarData(data = any(), relay = any()) } just runs
|
||||
every {
|
||||
getSnackbarDataFlow(relay = any(), relays = anyVararg())
|
||||
} returns mutableSnackbarDataFlow
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
@@ -149,7 +162,7 @@ class ViewSendViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on DeleteClick with success should display toast`() = runTest {
|
||||
fun `on DeleteClick with success should navigate back`() = runTest {
|
||||
val initialState = DEFAULT_STATE.copy(viewState = DEFAULT_CONTENT_VIEW_STATE)
|
||||
val sendView = createMockSendView(number = 1)
|
||||
every {
|
||||
@@ -175,15 +188,17 @@ class ViewSendViewModelTest : BaseViewModelTest() {
|
||||
initialState.copy(dialogState = null),
|
||||
stateFlow.awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
ViewSendEvent.ShowToast(message = R.string.send_deleted.asText()),
|
||||
eventFLow.awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
ViewSendEvent.NavigateBack,
|
||||
eventFLow.awaitItem(),
|
||||
)
|
||||
}
|
||||
verify(exactly = 1) {
|
||||
snackbarRelayManager.sendSnackbarData(
|
||||
data = BitwardenSnackbarData(message = R.string.send_deleted.asText()),
|
||||
relay = SnackbarRelay.SEND_DELETED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -390,6 +405,16 @@ class ViewSendViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SnackbarDataReceive should update emit ShowSnackbar`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
val snackbarData = BitwardenSnackbarData(message = "Test".asText())
|
||||
viewModel.eventFlow.test {
|
||||
mutableSnackbarDataFlow.tryEmit(snackbarData)
|
||||
assertEquals(ViewSendEvent.ShowSnackbar(data = snackbarData), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: ViewSendState? = null,
|
||||
): ViewSendViewModel = ViewSendViewModel(
|
||||
@@ -397,8 +422,8 @@ class ViewSendViewModelTest : BaseViewModelTest() {
|
||||
clock = FIXED_CLOCK,
|
||||
vaultRepository = vaultRepository,
|
||||
environmentRepository = environmentRepository,
|
||||
savedStateHandle = SavedStateHandle().apply
|
||||
{
|
||||
snackbarRelayManager = snackbarRelayManager,
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
set(key = "state", value = state)
|
||||
every { toViewSendArgs() } returns ViewSendArgs(
|
||||
sendId = (state ?: DEFAULT_STATE).sendId,
|
||||
|
||||
+11
@@ -37,6 +37,7 @@ import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialR
|
||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||
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.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
|
||||
@@ -368,6 +369,16 @@ class VaultItemListingScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowSnackbar event should display the snackbar`() {
|
||||
val message = "message"
|
||||
val data = BitwardenSnackbarData(message = message.asText())
|
||||
mutableEventFlow.tryEmit(VaultItemListingEvent.ShowSnackbar(data = data))
|
||||
composeTestRule
|
||||
.onNodeWithText(text = message)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `back gesture should send BackClick action`() {
|
||||
backDispatcher?.onBackPressed()
|
||||
|
||||
+22
@@ -15,6 +15,7 @@ import androidx.credentials.provider.PublicKeyCredentialEntry
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.core.data.util.asFailure
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
|
||||
@@ -86,7 +87,9 @@ import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
||||
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.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
||||
import com.x8bit.bitwarden.ui.vault.components.model.CreateVaultItemType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAttestationOptions
|
||||
@@ -112,6 +115,7 @@ import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
@@ -244,6 +248,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
every { callingRequest } returns mockk<CreatePublicKeyCredentialRequest>(relaxed = true)
|
||||
every { callingAppInfo } returns mockCallingAppInfo
|
||||
}
|
||||
private val mutableSnackbarDataFlow: MutableSharedFlow<BitwardenSnackbarData> =
|
||||
bufferedMutableSharedFlow()
|
||||
private val snackbarRelayManager: SnackbarRelayManager = mockk {
|
||||
every {
|
||||
getSnackbarDataFlow(relay = any(), relays = anyVararg())
|
||||
} returns mutableSnackbarDataFlow
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
@@ -4949,6 +4960,16 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SnackbarDataReceive should update emit ShowSnackbar`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
val snackbarData = BitwardenSnackbarData(message = "Test".asText())
|
||||
viewModel.eventFlow.test {
|
||||
mutableSnackbarDataFlow.tryEmit(snackbarData)
|
||||
assertEquals(VaultItemListingEvent.ShowSnackbar(data = snackbarData), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSavedStateHandleWithVaultItemListingType(
|
||||
vaultItemListingType: VaultItemListingType,
|
||||
): SavedStateHandle = SavedStateHandle().apply {
|
||||
@@ -4985,6 +5006,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
originManager = originManager,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
privilegedAppRepository = privilegedAppRepository,
|
||||
snackbarRelayManager = snackbarRelayManager,
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
||||
Reference in New Issue
Block a user