From 3c77a3d7aa56d2d14b9cd4ac644578c8770efaec Mon Sep 17 00:00:00 2001 From: Jeffrey Starke Date: Thu, 1 May 2025 17:14:54 -0400 Subject: [PATCH] Sticker management v2 - Implement multi-delete. --- .../stickers/StickerManagementActivityV2.kt | 35 ++++++++++++-- .../stickers/StickerManagementViewModelV2.kt | 47 +++++++++++++++++-- app/src/main/res/values/strings.xml | 5 ++ 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivityV2.kt index bb425c117d..4a061e037c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivityV2.kt @@ -60,6 +60,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.launch +import org.signal.core.ui.compose.Dialogs import org.signal.core.ui.compose.Dividers import org.signal.core.ui.compose.DropdownMenus import org.signal.core.ui.compose.Previews @@ -117,7 +118,9 @@ class StickerManagementActivityV2 : PassphraseRequiredActivity() { }, installedTabCallbacks = object : InstalledStickersContentCallbacks { override fun onForwardClick(pack: InstalledStickerPack) = openShareSheet(pack.id, pack.key) - override fun onRemoveClick(pack: InstalledStickerPack) = viewModel.uninstallStickerPack(pack) + override fun onRemoveClick(packIds: Set) = viewModel.onUninstallStickerPacksRequested(packIds) + override fun onRemoveStickerPacksConfirmed(packIds: Set) = viewModel.onUninstallStickerPacksConfirmed(packIds) + override fun onRemoveStickerPacksCanceled() = viewModel.onUninstallStickerPacksCanceled() override fun onSelectionToggle(pack: InstalledStickerPack) = viewModel.toggleSelection(pack) override fun onSelectAllToggle() = viewModel.toggleSelectAll() override fun onDragAndDropEvent(event: DragAndDropEvent) { @@ -165,14 +168,18 @@ interface AvailableStickersContentCallbacks { interface InstalledStickersContentCallbacks { fun onForwardClick(pack: InstalledStickerPack) - fun onRemoveClick(pack: InstalledStickerPack) + fun onRemoveClick(packIds: Set) + fun onRemoveStickerPacksConfirmed(packIds: Set) + fun onRemoveStickerPacksCanceled() fun onSelectionToggle(pack: InstalledStickerPack) fun onSelectAllToggle() fun onDragAndDropEvent(event: DragAndDropEvent) object Empty : InstalledStickersContentCallbacks { override fun onForwardClick(pack: InstalledStickerPack) = Unit - override fun onRemoveClick(pack: InstalledStickerPack) = Unit + override fun onRemoveClick(packIds: Set) = Unit + override fun onRemoveStickerPacksConfirmed(packIds: Set) = Unit + override fun onRemoveStickerPacksCanceled() = Unit override fun onSelectionToggle(pack: InstalledStickerPack) = Unit override fun onSelectAllToggle() = Unit override fun onDragAndDropEvent(event: DragAndDropEvent) = Unit @@ -207,6 +214,7 @@ private fun StickerManagementScreen( packs = uiState.installedPacks, multiSelectEnabled = uiState.multiSelectEnabled, selectedPackIds = uiState.selectedPackIds, + dialogState = uiState.userPrompt, callbacks = installedTabCallbacks ) } @@ -393,6 +401,7 @@ private fun InstalledStickersContent( packs: List, multiSelectEnabled: Boolean, selectedPackIds: Set, + dialogState: ConfirmRemoveStickerPacksPrompt? = null, callbacks: InstalledStickersContentCallbacks = InstalledStickersContentCallbacks.Empty, modifier: Modifier = Modifier ) { @@ -448,7 +457,7 @@ private fun InstalledStickersContent( menuController = menuController, onForwardClick = { callbacks.onForwardClick(pack) }, onSelectionToggle = { callbacks.onSelectionToggle(pack) }, - onRemoveClick = { callbacks.onRemoveClick(pack) }, + onRemoveClick = { callbacks.onRemoveClick(setOf(pack.id)) }, modifier = Modifier .shadow(if (isDragging) 1.dp else 0.dp) .combinedClickable( @@ -470,6 +479,22 @@ private fun InstalledStickersContent( } } + if (dialogState != null) { + Dialogs.SimpleAlertDialog( + title = Dialogs.NoTitle, + body = pluralStringResource( + R.plurals.StickerManagement_delete_n_packs_confirmation, + dialogState.numItemsToDelete, + NumberFormat.getInstance().format(dialogState.numItemsToDelete) + ), + confirm = stringResource(R.string.StickerManagement_menu_remove_pack), + dismiss = stringResource(android.R.string.cancel), + onConfirm = { callbacks.onRemoveStickerPacksConfirmed(selectedPackIds) }, + onDeny = callbacks::onRemoveStickerPacksCanceled, + onDismiss = callbacks::onRemoveStickerPacksCanceled + ) + } + SignalBottomActionBar( visible = multiSelectEnabled, items = listOf( @@ -485,7 +510,7 @@ private fun InstalledStickersContent( ActionItem( iconRes = R.drawable.symbol_trash_24, title = stringResource(R.string.StickerManagement_action_delete_selected), - action = { /* TODO implement multi-delete */ } + action = { callbacks.onRemoveClick(selectedPackIds) } ) ), modifier = Modifier diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModelV2.kt b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModelV2.kt index c14095d6ff..abe0dbef4e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModelV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModelV2.kt @@ -72,7 +72,8 @@ class StickerManagementViewModelV2 : ViewModel() { previousState.copy( availableBlessedPacks = availableBlessedPacks, availableNotBlessedPacks = availableNotBlessedPacks, - installedPacks = installedPacks + installedPacks = installedPacks, + multiSelectEnabled = if (installedPacks.isEmpty()) false else previousState.multiSelectEnabled ) } } @@ -98,9 +99,42 @@ class StickerManagementViewModelV2 : ViewModel() { } } - fun uninstallStickerPack(pack: InstalledStickerPack) { + fun onUninstallStickerPacksRequested(packIds: Set) { + if (packIds.isEmpty()) { + return + } + + if (_uiState.value.multiSelectEnabled) { + _uiState.update { previousState -> + previousState.copy( + userPrompt = ConfirmRemoveStickerPacksPrompt(numItemsToDelete = packIds.size) + ) + } + } else { + uninstallStickerPacks(packIds) + } + } + + fun onUninstallStickerPacksConfirmed(packIds: Set) { + _uiState.update { previousState -> previousState.copy(userPrompt = null) } + uninstallStickerPacks(packIds) + } + + fun onUninstallStickerPacksCanceled() { + _uiState.update { previousState -> previousState.copy(userPrompt = null) } + } + + private fun uninstallStickerPacks(packIds: Set) { viewModelScope.launch { - StickerManagementRepository.uninstallStickerPack(packId = pack.id, packKey = pack.key) + _uiState.value.installedPacks + .filter { packIds.contains(it.id) } + .forEach { pack -> StickerManagementRepository.uninstallStickerPack(packId = pack.id, packKey = pack.key) } + + _uiState.update { previousState -> + previousState.copy( + selectedPackIds = previousState.selectedPackIds.minus(packIds) + ) + } } } @@ -152,7 +186,12 @@ data class StickerManagementUiState( val availableNotBlessedPacks: List = emptyList(), val installedPacks: List = emptyList(), val multiSelectEnabled: Boolean = false, - val selectedPackIds: Set = emptySet() + val selectedPackIds: Set = emptySet(), + val userPrompt: ConfirmRemoveStickerPacksPrompt? = null +) + +data class ConfirmRemoveStickerPacksPrompt( + val numItemsToDelete: Int ) data class AvailableStickerPack( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c8e10de923..12cfa0d8e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2830,6 +2830,11 @@ Deselect all Delete + + + Remove %1$s sticker pack + Remove %1$s sticker packs + Forward