BIT-2287: Display a dialog for unassigned items (#1358)

This commit is contained in:
David Perez
2024-05-13 09:11:26 -05:00
committed by Álison Fernandes
parent 8e1ecd1e6c
commit 04ac479e7d
12 changed files with 364 additions and 2 deletions

View File

@@ -73,6 +73,17 @@ interface SettingsDiskSource {
*/
fun clearData(userId: String)
/**
* Retrieves the preference indicating whether we should check for unassigned organization
* ciphers.
*/
fun getShouldCheckOrgUnassignedItems(userId: String): Boolean?
/**
* Stores the given [shouldCheckOrgUnassignedItems] for the given [userId].
*/
fun storeShouldCheckOrgUnassignedItems(userId: String, shouldCheckOrgUnassignedItems: Boolean?)
/**
* Retrieves the biometric integrity validity for the given [userId] and
* [systemBioIntegrityState].

View File

@@ -33,6 +33,7 @@ private const val CRASH_LOGGING_ENABLED_KEY = "crashLoggingEnabled"
private const val CLEAR_CLIPBOARD_INTERVAL_KEY = "clearClipboard"
private const val INITIAL_AUTOFILL_DIALOG_SHOWN = "addSitePromptShown"
private const val HAS_USER_LOGGED_IN_OR_CREATED_AN_ACCOUNT_KEY = "hasUserLoggedInOrCreatedAccount"
private const val SHOULD_CHECK_ORG_UNASSIGNED_ITEMS = "shouldCheckOrganizationUnassignedItems"
/**
* Primary implementation of [SettingsDiskSource].
@@ -154,6 +155,7 @@ class SettingsDiskSourceImpl(
storeBlockedAutofillUris(userId = userId, blockedAutofillUris = null)
storeLastSyncTime(userId = userId, lastSyncTime = null)
storeClearClipboardFrequencySeconds(userId = userId, frequency = null)
storeShouldCheckOrgUnassignedItems(userId = userId, shouldCheckOrgUnassignedItems = null)
removeWithPrefix(prefix = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY.appendIdentifier(userId))
// The following are intentionally not cleared so they can be
@@ -184,6 +186,19 @@ class SettingsDiskSourceImpl(
)
}
override fun getShouldCheckOrgUnassignedItems(userId: String): Boolean? =
getBoolean(key = SHOULD_CHECK_ORG_UNASSIGNED_ITEMS.appendIdentifier(userId))
override fun storeShouldCheckOrgUnassignedItems(
userId: String,
shouldCheckOrgUnassignedItems: Boolean?,
) {
putBoolean(
key = SHOULD_CHECK_ORG_UNASSIGNED_ITEMS.appendIdentifier(userId),
value = shouldCheckOrgUnassignedItems,
)
}
override fun getAutoCopyTotpDisabled(userId: String): Boolean? =
getBoolean(key = DISABLE_AUTO_TOTP_COPY_KEY.appendIdentifier(userId))

View File

@@ -327,7 +327,18 @@ interface VaultRepository : VaultLockManager {
suspend fun updateFolder(folderId: String, folderView: FolderView): UpdateFolderResult
/**
* Attempt to get the user's vault data for export.
* Attempt to get the user's vault data for export.
*/
suspend fun exportVaultDataToString(format: ExportFormat): ExportVaultDataResult
/**
* Checks if the user should see the unassigned items message.
*/
suspend fun shouldShowUnassignedItemsInfo(): Boolean
/**
* Sets the value indicating that the user has or has not acknowledged that their organization
* has unassigned items.
*/
fun acknowledgeUnassignedItemsInfo(hasAcknowledged: Boolean)
}

View File

@@ -1258,6 +1258,26 @@ class VaultRepositoryImpl(
)
}
@Suppress("ReturnCount")
override suspend fun shouldShowUnassignedItemsInfo(): Boolean {
val userId = activeUserId ?: return false
if (settingsDiskSource.getShouldCheckOrgUnassignedItems(userId = userId) == false) {
return false
}
return ciphersService.hasUnassignedCiphers().fold(
onFailure = { false },
onSuccess = { it },
)
}
override fun acknowledgeUnassignedItemsInfo(hasAcknowledged: Boolean) {
val userId = activeUserId ?: return
settingsDiskSource.storeShouldCheckOrgUnassignedItems(
userId = userId,
shouldCheckOrgUnassignedItems = !hasAcknowledged,
)
}
/**
* Checks if the given [userId] has an associated encrypted PIN key but not a pin-protected user
* key. This indicates a scenario in which a user has requested PIN unlocking but requires

View File

@@ -355,6 +355,18 @@ private fun VaultDialogs(
onDismissRequest = vaultHandlers.dialogDismiss,
)
VaultState.DialogState.UnassignedItems -> BitwardenTwoButtonDialog(
title = stringResource(id = R.string.notice),
message = stringResource(
id = R.string.organization_unassigned_items_message_useu_description_long,
),
confirmButtonText = stringResource(id = R.string.remind_me_later),
dismissButtonText = stringResource(id = R.string.ok),
onConfirmClick = vaultHandlers.dialogDismiss,
onDismissClick = vaultHandlers.unassignedItemsAcknowledgeClick,
onDismissRequest = vaultHandlers.dialogDismiss,
)
null -> Unit
}
}

View File

@@ -122,6 +122,11 @@ class VaultViewModel @Inject constructor(
sendAction(VaultAction.Internal.UserStateUpdateReceive(userState = it))
}
.launchIn(viewModelScope)
viewModelScope.launch {
val result = vaultRepository.shouldShowUnassignedItemsInfo()
sendAction(VaultAction.Internal.ReceiveUnassignedItemsResult(result))
}
}
override fun handleAction(action: VaultAction) {
@@ -154,6 +159,7 @@ class VaultViewModel @Inject constructor(
handleMasterPasswordRepromptSubmit(action)
}
VaultAction.UnassignedItemsAcknowledgeClick -> handleUnassignedItemsAcknowledgeClick()
is VaultAction.Internal -> handleInternalAction(action)
}
}
@@ -350,6 +356,11 @@ class VaultViewModel @Inject constructor(
}
}
private fun handleUnassignedItemsAcknowledgeClick() {
vaultRepository.acknowledgeUnassignedItemsInfo(hasAcknowledged = true)
mutableStateFlow.update { it.copy(dialog = null) }
}
private fun handleCopyNoteClick(action: ListingItemOverflowAction.VaultAction.CopyNoteClick) {
clipboardManager.setText(action.notes)
}
@@ -409,6 +420,10 @@ class VaultViewModel @Inject constructor(
handlePullToRefreshEnableReceive(action)
}
is VaultAction.Internal.ReceiveUnassignedItemsResult -> {
handleReceiveUnassignedItemsResult(action)
}
is VaultAction.Internal.UserStateUpdateReceive -> handleUserStateUpdateReceive(action)
is VaultAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
is VaultAction.Internal.IconLoadingSettingReceive -> handleIconLoadingSettingReceive(
@@ -440,6 +455,14 @@ class VaultViewModel @Inject constructor(
}
}
private fun handleReceiveUnassignedItemsResult(
action: VaultAction.Internal.ReceiveUnassignedItemsResult,
) {
if (action.result) {
mutableStateFlow.update { it.copy(dialog = VaultState.DialogState.UnassignedItems) }
}
}
private fun handleUserStateUpdateReceive(action: VaultAction.Internal.UserStateUpdateReceive) {
// Leave the current data alone if there is no UserState; we are in the process of logging
// out.
@@ -518,7 +541,13 @@ class VaultViewModel @Inject constructor(
hasMasterPassword = state.hasMasterPassword,
vaultFilterType = vaultFilterTypeOrDefault,
),
dialog = null,
dialog = when (it.dialog) {
VaultState.DialogState.UnassignedItems -> VaultState.DialogState.UnassignedItems
is VaultState.DialogState.Error,
VaultState.DialogState.Syncing,
null,
-> null
},
)
}
sendEvent(VaultEvent.DismissPullToRefresh)
@@ -900,6 +929,12 @@ data class VaultState(
val title: Text,
val message: Text,
) : DialogState()
/**
* Represents a dialog indicating that the user has unassigned items.
*/
@Parcelize
data object UnassignedItems : DialogState()
}
}
@@ -1114,6 +1149,11 @@ sealed class VaultAction {
val password: String,
) : VaultAction()
/**
* The user has acknowledged the unassigned items and we do not want to show the message again.
*/
data object UnassignedItemsAcknowledgeClick : VaultAction()
/**
* Models actions that the [VaultViewModel] itself might send.
*/
@@ -1138,6 +1178,14 @@ sealed class VaultAction {
*/
data class PullToRefreshEnableReceive(val isPullToRefreshEnabled: Boolean) : Internal()
/**
* Indicates that we have received the data concerning whether we should display the
* unassigned items dialog.
*/
data class ReceiveUnassignedItemsResult(
val result: Boolean,
) : Internal()
/**
* Indicates a change in user state has been received.
*/

View File

@@ -34,6 +34,7 @@ data class VaultHandlers(
val dialogDismiss: () -> Unit,
val overflowOptionClick: (ListingItemOverflowAction.VaultAction) -> Unit,
val masterPasswordRepromptSubmit: (ListingItemOverflowAction.VaultAction, String) -> Unit,
val unassignedItemsAcknowledgeClick: () -> Unit,
) {
companion object {
/**
@@ -88,6 +89,9 @@ data class VaultHandlers(
),
)
},
unassignedItemsAcknowledgeClick = {
viewModel.trySendAction(VaultAction.UnassignedItemsAcknowledgeClick)
},
)
}
}