Sticker management v2 – Improve list animations and state transitions.

- Uninstall selected packs in a single database transaction to avoid UI flickering.
- Add section header keys to prevent them from animating wildly while scrolling.
This commit is contained in:
Jeffrey Starke
2025-05-14 17:10:41 -04:00
committed by GitHub
parent 8b828677de
commit f3a475d0c8
5 changed files with 38 additions and 18 deletions

View File

@@ -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<StickerPackId>) {
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()

View File

@@ -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.")

View File

@@ -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()
)
}
}

View File

@@ -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<StickerPackId, StickerPackKey>) = 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))
}
}
}

View File

@@ -133,12 +133,7 @@ class StickerManagementViewModel : ViewModel() {
private fun uninstallStickerPacks(packIds: Set<StickerPackId>) {
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)
)
}
}