From f3a475d0c8c3bf3d0fbcd72fda014400e4bdd5b1 Mon Sep 17 00:00:00 2001 From: Jeffrey Starke Date: Wed, 14 May 2025 17:10:41 -0400 Subject: [PATCH] =?UTF-8?q?Sticker=20management=20v2=20=E2=80=93=C2=A0Impr?= =?UTF-8?q?ove=20list=20animations=20and=20state=20transitions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Uninstall selected packs in a single database transaction to avoid UI flickering. - Add section header keys to prevent them from animating wildly while scrolling. --- .../securesms/database/StickerTable.kt | 11 ++++++++-- .../messages/SyncMessageProcessor.kt | 3 ++- .../stickers/StickerManagementActivity.kt | 22 +++++++++++++++---- .../stickers/StickerManagementRepository.kt | 10 +++++---- .../stickers/StickerManagementViewModel.kt | 10 +++------ 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/StickerTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/StickerTable.kt index c40fca2134..49568080d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/StickerTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/StickerTable.kt @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecret import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream import org.thoughtcrime.securesms.database.model.IncomingSticker +import org.thoughtcrime.securesms.database.model.StickerPackId import org.thoughtcrime.securesms.database.model.StickerPackRecord import org.thoughtcrime.securesms.database.model.StickerRecord import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri @@ -296,9 +297,15 @@ class StickerTable( } fun uninstallPack(packId: String) { + uninstallPacks(setOf(StickerPackId(packId))) + } + + fun uninstallPacks(packIds: Set) { writableDatabase.withinTransaction { db -> - updatePackInstalled(db = db, packId = packId, installed = false, notify = false) - deleteStickersInPackExceptCover(db, packId) + packIds.forEach { packId -> + updatePackInstalled(db = db, packId = packId.value, installed = false, notify = false) + deleteStickersInPackExceptCover(db, packId.value) + } } notifyStickerPackListeners() diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt index f29ac4cb55..5191459d33 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt @@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.ParentStoryId import org.thoughtcrime.securesms.database.model.ParentStoryId.DirectReply import org.thoughtcrime.securesms.database.model.ParentStoryId.GroupReply +import org.thoughtcrime.securesms.database.model.StickerPackId import org.thoughtcrime.securesms.database.model.StoryType import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge @@ -1075,7 +1076,7 @@ object SyncMessageProcessor { when (operation.type!!) { StickerPackOperation.Type.INSTALL -> jobManager.add(StickerPackDownloadJob.forInstall(packId, packKey, false)) - StickerPackOperation.Type.REMOVE -> SignalDatabase.stickers.uninstallPack(packId) + StickerPackOperation.Type.REMOVE -> SignalDatabase.stickers.uninstallPacks(setOf(StickerPackId(packId))) } } else { warn("Received incomplete sticker pack operation sync.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.kt index 53c821ea7d..48d9211ec9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.kt @@ -412,7 +412,13 @@ private fun AvailableStickersContent( modifier = modifier.fillMaxHeight() ) { if (blessedPacks.isNotEmpty()) { - item { StickerPackSectionHeader(text = stringResource(R.string.StickerManagement_signal_artist_series_header)) } + item(key = "blessed_section_header") { + StickerPackSectionHeader( + text = stringResource(R.string.StickerManagement_signal_artist_series_header), + modifier = Modifier.animateItem() + ) + } + items( items = blessedPacks, key = { it.id.value } @@ -442,7 +448,12 @@ private fun AvailableStickersContent( } if (notBlessedPacks.isNotEmpty()) { - item { StickerPackSectionHeader(text = stringResource(R.string.StickerManagement_stickers_you_received_header)) } + item(key = "not_blessed_section_header") { + StickerPackSectionHeader( + text = stringResource(R.string.StickerManagement_stickers_you_received_header), + modifier = Modifier.animateItem() + ) + } items( items = notBlessedPacks, key = { it.id.value } @@ -508,9 +519,12 @@ private fun InstalledStickersContent( rightDpOffset = if (isRtl) 56.dp else screenWidth ) ) { - item { + item(key = "installed_section_header") { DraggableItem(dragDropState, 0) { - StickerPackSectionHeader(text = stringResource(R.string.StickerManagement_installed_stickers_header)) + StickerPackSectionHeader( + text = stringResource(R.string.StickerManagement_installed_stickers_header), + modifier = Modifier.animateItem() + ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementRepository.kt index 8bae4cb676..735803e2fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementRepository.kt @@ -118,15 +118,17 @@ object StickerManagementRepository { @Discouraged("For Java use only. In Kotlin, use uninstallStickerPack() instead.") fun uninstallStickerPackAsync(packId: String, packKey: String) { coroutineScope.launch { - uninstallStickerPack(StickerPackId(packId), StickerPackKey(packKey)) + uninstallStickerPacks(mapOf(StickerPackId(packId) to StickerPackKey(packKey))) } } - suspend fun uninstallStickerPack(packId: StickerPackId, packKey: StickerPackKey) = withContext(Dispatchers.IO) { - stickersDbTable.uninstallPack(packId.value) + suspend fun uninstallStickerPacks(packKeysById: Map) = withContext(Dispatchers.IO) { + stickersDbTable.uninstallPacks(packIds = packKeysById.keys) if (SignalStore.account.hasLinkedDevices) { - AppDependencies.jobManager.add(MultiDeviceStickerPackOperationJob(packId.value, packKey.value, MultiDeviceStickerPackOperationJob.Type.REMOVE)) + packKeysById.forEach { (packId, packKey) -> + AppDependencies.jobManager.add(MultiDeviceStickerPackOperationJob(packId.value, packKey.value, MultiDeviceStickerPackOperationJob.Type.REMOVE)) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModel.kt index f0c8cb3c36..0017f6e373 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModel.kt @@ -133,12 +133,7 @@ class StickerManagementViewModel : ViewModel() { private fun uninstallStickerPacks(packIds: Set) { val packsToUninstall = _uiState.value.installedPacks.filter { packIds.contains(it.id) } viewModelScope.launch { - packsToUninstall.forEach { pack -> - StickerManagementRepository.uninstallStickerPack(packId = pack.id, packKey = pack.key) - _uiState.update { previousState -> - previousState.copy(selectedPackIds = previousState.selectedPackIds.minus(pack.id)) - } - } + StickerManagementRepository.uninstallStickerPacks(packsToUninstall.associate { it.id to it.key }) _uiState.update { previousState -> previousState.copy( @@ -146,7 +141,8 @@ class StickerManagementViewModel : ViewModel() { StickerManagementConfirmation.UninstalledPack(packsToUninstall.single().record.title) } else { StickerManagementConfirmation.UninstalledPacks(packsToUninstall.size) - } + }, + selectedPackIds = previousState.selectedPackIds.minus(packIds) ) } }