mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-26 22:20:20 +00:00
Sticker management v2 - Implement multi-select.
This commit is contained in:
committed by
Cody Henthorne
parent
030678b029
commit
c8bfc88bed
@@ -10,6 +10,21 @@ import android.view.animation.AnimationUtils
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
@@ -20,7 +35,7 @@ import org.thoughtcrime.securesms.util.ViewUtil
|
||||
*
|
||||
* Overflow items are rendered in a [SignalContextMenu].
|
||||
*/
|
||||
class SignalBottomActionBar(context: Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {
|
||||
class SignalBottomActionBar(context: Context, attributeSet: AttributeSet?) : LinearLayout(context, attributeSet) {
|
||||
|
||||
val items: MutableList<ActionItem> = mutableListOf()
|
||||
|
||||
@@ -118,3 +133,46 @@ class SignalBottomActionBar(context: Context, attributeSet: AttributeSet) : Line
|
||||
view.setOnClickListener { item.action.run() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SignalBottomActionBar(
|
||||
visible: Boolean = true,
|
||||
items: List<ActionItem>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val slideAnimationOffset = with(LocalDensity.current) { 40.dp.roundToPx() }
|
||||
|
||||
val enterAnimation = slideInVertically(
|
||||
animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing),
|
||||
initialOffsetY = { slideAnimationOffset }
|
||||
) + fadeIn(
|
||||
animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
|
||||
)
|
||||
|
||||
val exitAnimation = slideOutVertically(
|
||||
animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing),
|
||||
targetOffsetY = { slideAnimationOffset }
|
||||
) + fadeOut(
|
||||
animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = enterAnimation,
|
||||
exit = exitAnimation,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp, vertical = 16.dp)
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
SignalBottomActionBar(context, null)
|
||||
.apply { setItems(items) }
|
||||
},
|
||||
update = { view ->
|
||||
view.setItems(items)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
@@ -32,19 +34,25 @@ import androidx.compose.material3.TabRowDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@@ -64,6 +72,8 @@ import org.signal.core.ui.compose.copied.androidx.compose.rememberDragDropState
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem
|
||||
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackId
|
||||
@@ -71,6 +81,7 @@ import org.thoughtcrime.securesms.database.model.StickerPackKey
|
||||
import org.thoughtcrime.securesms.sharing.MultiShareArgs
|
||||
import org.thoughtcrime.securesms.stickers.AvailableStickerPack.DownloadStatus
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import java.text.NumberFormat
|
||||
|
||||
/**
|
||||
* Displays all of the available and installed sticker packs, enabling installation, uninstallation, and sorting.
|
||||
@@ -99,14 +110,16 @@ class StickerManagementActivityV2 : PassphraseRequiredActivity() {
|
||||
StickerManagementScreen(
|
||||
uiState = uiState,
|
||||
onNavigateBack = ::supportFinishAfterTransition,
|
||||
onExitMultiSelectMode = { viewModel.setMultiSelectEnabled(false) },
|
||||
availableTabCallbacks = object : AvailableStickersContentCallbacks {
|
||||
override fun onForwardClick(pack: AvailableStickerPack) = openShareSheet(pack.id, pack.key)
|
||||
override fun onInstallClick(pack: AvailableStickerPack) = viewModel.installStickerPack(pack)
|
||||
},
|
||||
installedTabCallbacks = object : InstalledStickersContentCallbacks {
|
||||
override fun onForwardClick(pack: InstalledStickerPack) = openShareSheet(pack.id, pack.key)
|
||||
override fun onSelectClick(pack: InstalledStickerPack) = viewModel.toggleSelection(pack)
|
||||
override fun onRemoveClick(pack: InstalledStickerPack) = viewModel.uninstallStickerPack(pack)
|
||||
override fun onSelectionToggle(pack: InstalledStickerPack) = viewModel.toggleSelection(pack)
|
||||
override fun onSelectAllToggle() = viewModel.toggleSelectAll()
|
||||
override fun onDragAndDropEvent(event: DragAndDropEvent) {
|
||||
when (event) {
|
||||
is DragAndDropEvent.OnItemMove -> viewModel.updatePosition(event.fromIndex, event.toIndex)
|
||||
@@ -152,14 +165,16 @@ interface AvailableStickersContentCallbacks {
|
||||
|
||||
interface InstalledStickersContentCallbacks {
|
||||
fun onForwardClick(pack: InstalledStickerPack)
|
||||
fun onSelectClick(pack: InstalledStickerPack)
|
||||
fun onRemoveClick(pack: InstalledStickerPack)
|
||||
fun onSelectionToggle(pack: InstalledStickerPack)
|
||||
fun onSelectAllToggle()
|
||||
fun onDragAndDropEvent(event: DragAndDropEvent)
|
||||
|
||||
object Empty : InstalledStickersContentCallbacks {
|
||||
override fun onForwardClick(pack: InstalledStickerPack) = Unit
|
||||
override fun onSelectClick(pack: InstalledStickerPack) = Unit
|
||||
override fun onRemoveClick(pack: InstalledStickerPack) = Unit
|
||||
override fun onSelectionToggle(pack: InstalledStickerPack) = Unit
|
||||
override fun onSelectAllToggle() = Unit
|
||||
override fun onDragAndDropEvent(event: DragAndDropEvent) = Unit
|
||||
}
|
||||
}
|
||||
@@ -169,39 +184,50 @@ interface InstalledStickersContentCallbacks {
|
||||
private fun StickerManagementScreen(
|
||||
uiState: StickerManagementUiState,
|
||||
onNavigateBack: () -> Unit = {},
|
||||
onExitMultiSelectMode: () -> Unit = {},
|
||||
availableTabCallbacks: AvailableStickersContentCallbacks = AvailableStickersContentCallbacks.Empty,
|
||||
installedTabCallbacks: InstalledStickersContentCallbacks = InstalledStickersContentCallbacks.Empty,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { TopAppBar(onBackPress = onNavigateBack) }
|
||||
) { padding ->
|
||||
|
||||
val pages = listOf(
|
||||
Page(
|
||||
title = stringResource(R.string.StickerManagement_available_tab_label),
|
||||
getContent = {
|
||||
AvailableStickersContent(
|
||||
blessedPacks = uiState.availableBlessedPacks,
|
||||
notBlessedPacks = uiState.availableNotBlessedPacks,
|
||||
callbacks = availableTabCallbacks
|
||||
)
|
||||
}
|
||||
),
|
||||
Page(
|
||||
title = stringResource(R.string.StickerManagement_installed_tab_label),
|
||||
getContent = {
|
||||
InstalledStickersContent(
|
||||
packs = uiState.installedPacks,
|
||||
callbacks = installedTabCallbacks
|
||||
)
|
||||
}
|
||||
)
|
||||
val pages = listOf(
|
||||
Page(
|
||||
title = stringResource(R.string.StickerManagement_available_tab_label),
|
||||
getContent = {
|
||||
AvailableStickersContent(
|
||||
blessedPacks = uiState.availableBlessedPacks,
|
||||
notBlessedPacks = uiState.availableNotBlessedPacks,
|
||||
callbacks = availableTabCallbacks
|
||||
)
|
||||
}
|
||||
),
|
||||
Page(
|
||||
title = stringResource(R.string.StickerManagement_installed_tab_label),
|
||||
getContent = {
|
||||
InstalledStickersContent(
|
||||
packs = uiState.installedPacks,
|
||||
multiSelectEnabled = uiState.multiSelectEnabled,
|
||||
selectedPackIds = uiState.selectedPackIds,
|
||||
callbacks = installedTabCallbacks
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val pagerState = rememberPagerState(pageCount = { pages.size })
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState(pageCount = { pages.size })
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
if (pagerState.currentPage == 1 && uiState.multiSelectEnabled) {
|
||||
MultiSelectTopAppBar(
|
||||
selectedItemCount = uiState.selectedPackIds.size,
|
||||
onExitClick = onExitMultiSelectMode
|
||||
)
|
||||
} else {
|
||||
TopAppBar(onBackPress = onNavigateBack)
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = modifier.padding(padding)
|
||||
) {
|
||||
@@ -248,6 +274,21 @@ private fun TopAppBar(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MultiSelectTopAppBar(
|
||||
selectedItemCount: Int,
|
||||
onExitClick: () -> Unit = {}
|
||||
) {
|
||||
Scaffolds.DefaultTopAppBar(
|
||||
title = pluralStringResource(R.plurals.StickerManagement_title_n_selected, selectedItemCount, NumberFormat.getInstance().format(selectedItemCount)),
|
||||
titleContent = { _, title -> Text(text = title, style = MaterialTheme.typography.titleLarge) },
|
||||
navigationIconPainter = painterResource(R.drawable.symbol_x_24),
|
||||
navigationContentDescription = stringResource(R.string.StickerManagement_accessibility_exit_multi_select_mode),
|
||||
onNavigationClick = onExitClick
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PagerTab(
|
||||
title: String,
|
||||
@@ -285,6 +326,7 @@ private fun AvailableStickersContent(
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(top = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = modifier.fillMaxHeight()
|
||||
) {
|
||||
if (blessedPacks.isNotEmpty()) {
|
||||
@@ -349,6 +391,8 @@ private fun AvailableStickersContent(
|
||||
@Composable
|
||||
private fun InstalledStickersContent(
|
||||
packs: List<InstalledStickerPack>,
|
||||
multiSelectEnabled: Boolean,
|
||||
selectedPackIds: Set<StickerPackId>,
|
||||
callbacks: InstalledStickersContentCallbacks = InstalledStickersContentCallbacks.Empty,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@@ -360,54 +404,96 @@ private fun InstalledStickersContent(
|
||||
|
||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
|
||||
val density = LocalDensity.current
|
||||
val haptics = LocalHapticFeedback.current
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(top = 8.dp),
|
||||
state = listState,
|
||||
modifier = modifier
|
||||
.fillMaxHeight()
|
||||
.dragContainer(
|
||||
dragDropState = dragDropState,
|
||||
leftDpOffset = if (isRtl) 0.dp else screenWidth - 56.dp,
|
||||
rightDpOffset = if (isRtl) 56.dp else screenWidth
|
||||
)
|
||||
) {
|
||||
item {
|
||||
DraggableItem(dragDropState, 0) {
|
||||
StickerPackSectionHeader(text = stringResource(R.string.StickerManagement_installed_stickers_header))
|
||||
}
|
||||
}
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
var bottomActionBarPadding: Dp by remember { mutableStateOf(0.dp) }
|
||||
|
||||
itemsIndexed(
|
||||
items = packs,
|
||||
key = { _, pack -> pack.id.value }
|
||||
) { index, pack ->
|
||||
val menuController = remember { DropdownMenus.MenuController() }
|
||||
|
||||
DraggableItem(
|
||||
index = index + 1,
|
||||
dragDropState = dragDropState
|
||||
) { isDragging ->
|
||||
InstalledStickerPackRow(
|
||||
pack = pack,
|
||||
menuController = menuController,
|
||||
onForwardClick = { callbacks.onForwardClick(pack) },
|
||||
onSelectClick = { callbacks.onSelectClick(pack) },
|
||||
onRemoveClick = { callbacks.onRemoveClick(pack) },
|
||||
modifier = Modifier
|
||||
.shadow(if (isDragging) 1.dp else 0.dp)
|
||||
.combinedClickable(
|
||||
onClick = {},
|
||||
onLongClick = {
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
menuController.show()
|
||||
},
|
||||
onLongClickLabel = stringResource(R.string.StickerManagement_accessibility_open_context_menu)
|
||||
)
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(
|
||||
top = 8.dp,
|
||||
bottom = if (multiSelectEnabled) bottomActionBarPadding else 0.dp
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
state = listState,
|
||||
modifier = modifier
|
||||
.fillMaxHeight()
|
||||
.dragContainer(
|
||||
dragDropState = dragDropState,
|
||||
leftDpOffset = if (isRtl) 0.dp else screenWidth - 56.dp,
|
||||
rightDpOffset = if (isRtl) 56.dp else screenWidth
|
||||
)
|
||||
) {
|
||||
item {
|
||||
DraggableItem(dragDropState, 0) {
|
||||
StickerPackSectionHeader(text = stringResource(R.string.StickerManagement_installed_stickers_header))
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
items = packs,
|
||||
key = { _, pack -> pack.id.value }
|
||||
) { index, pack ->
|
||||
val menuController = remember { DropdownMenus.MenuController() }
|
||||
|
||||
DraggableItem(
|
||||
index = index + 1,
|
||||
dragDropState = dragDropState
|
||||
) { isDragging ->
|
||||
InstalledStickerPackRow(
|
||||
pack = pack,
|
||||
multiSelectEnabled = multiSelectEnabled,
|
||||
selected = pack.id in selectedPackIds,
|
||||
menuController = menuController,
|
||||
onForwardClick = { callbacks.onForwardClick(pack) },
|
||||
onSelectionToggle = { callbacks.onSelectionToggle(pack) },
|
||||
onRemoveClick = { callbacks.onRemoveClick(pack) },
|
||||
modifier = Modifier
|
||||
.shadow(if (isDragging) 1.dp else 0.dp)
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
if (multiSelectEnabled) {
|
||||
callbacks.onSelectionToggle(pack)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
if (!multiSelectEnabled) {
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
menuController.show()
|
||||
}
|
||||
},
|
||||
onLongClickLabel = stringResource(R.string.StickerManagement_accessibility_open_context_menu)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SignalBottomActionBar(
|
||||
visible = multiSelectEnabled,
|
||||
items = listOf(
|
||||
ActionItem(
|
||||
iconRes = R.drawable.symbol_check_circle_24,
|
||||
title = if (selectedPackIds.size == packs.size) {
|
||||
stringResource(R.string.StickerManagement_action_deselect_all)
|
||||
} else {
|
||||
stringResource(R.string.StickerManagement_action_select_all)
|
||||
},
|
||||
action = callbacks::onSelectAllToggle
|
||||
),
|
||||
ActionItem(
|
||||
iconRes = R.drawable.symbol_trash_24,
|
||||
title = stringResource(R.string.StickerManagement_action_delete_selected),
|
||||
action = { /* TODO implement multi-delete */ }
|
||||
)
|
||||
),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.onGloballyPositioned { layoutCoordinates ->
|
||||
bottomActionBarPadding = with(density) { layoutCoordinates.size.height.toDp() }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,6 +564,7 @@ private fun InstalledStickersContentPreview() {
|
||||
isBlessed = true
|
||||
),
|
||||
StickerPreviewDataFactory.installedPack(
|
||||
packId = "stickerPackId2",
|
||||
title = "Bandit the Cat",
|
||||
author = "Agnes Lee",
|
||||
isBlessed = true
|
||||
@@ -486,7 +573,9 @@ private fun InstalledStickersContentPreview() {
|
||||
title = "Day by Day",
|
||||
author = "Miguel Ángel Camprubí"
|
||||
)
|
||||
)
|
||||
),
|
||||
multiSelectEnabled = true,
|
||||
selectedPackIds = setOf(StickerPackId("stickerPackId2"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,9 +116,32 @@ class StickerManagementViewModelV2 : ViewModel() {
|
||||
|
||||
fun toggleSelection(pack: InstalledStickerPack) {
|
||||
_uiState.update { previousState ->
|
||||
val wasItemSelected = previousState.selectedPackIds.contains(pack.id)
|
||||
previousState.copy(
|
||||
isMultiSelectMode = true,
|
||||
selectedPackIds = if (pack.isSelected) previousState.selectedPackIds.minus(pack.id) else previousState.selectedPackIds.plus(pack.id)
|
||||
multiSelectEnabled = true,
|
||||
selectedPackIds = if (wasItemSelected) previousState.selectedPackIds.minus(pack.id) else previousState.selectedPackIds.plus(pack.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleSelectAll() {
|
||||
_uiState.update { previousState ->
|
||||
previousState.copy(
|
||||
multiSelectEnabled = true,
|
||||
selectedPackIds = if (previousState.selectedPackIds.size == previousState.installedPacks.size) {
|
||||
emptySet()
|
||||
} else {
|
||||
previousState.installedPacks.map { it.id }.toSet()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setMultiSelectEnabled(isEnabled: Boolean) {
|
||||
_uiState.update { previousState ->
|
||||
previousState.copy(
|
||||
multiSelectEnabled = isEnabled,
|
||||
selectedPackIds = emptySet()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -128,7 +151,7 @@ data class StickerManagementUiState(
|
||||
val availableBlessedPacks: List<AvailableStickerPack> = emptyList(),
|
||||
val availableNotBlessedPacks: List<AvailableStickerPack> = emptyList(),
|
||||
val installedPacks: List<InstalledStickerPack> = emptyList(),
|
||||
val isMultiSelectMode: Boolean = false,
|
||||
val multiSelectEnabled: Boolean = false,
|
||||
val selectedPackIds: Set<StickerPackId> = emptySet()
|
||||
)
|
||||
|
||||
@@ -150,8 +173,7 @@ data class AvailableStickerPack(
|
||||
data class InstalledStickerPack(
|
||||
val record: StickerPackRecord,
|
||||
val isBlessed: Boolean,
|
||||
val sortOrder: Int,
|
||||
val isSelected: Boolean = false
|
||||
val sortOrder: Int
|
||||
) {
|
||||
val id = StickerPackId(record.packId)
|
||||
val key = StickerPackKey(record.packKey)
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
package org.thoughtcrime.securesms.stickers
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkHorizontally
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -13,6 +18,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -64,8 +70,12 @@ fun AvailableStickerPackRow(
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(start = 24.dp, top = 12.dp, end = 12.dp, bottom = 12.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(18.dp)
|
||||
)
|
||||
.padding(vertical = 10.dp)
|
||||
) {
|
||||
StickerPackInfo(
|
||||
coverImageUri = DecryptableUri(pack.record.cover.uri),
|
||||
@@ -132,25 +142,32 @@ fun AvailableStickerPackRow(
|
||||
@Composable
|
||||
fun InstalledStickerPackRow(
|
||||
pack: InstalledStickerPack,
|
||||
multiSelectModeEnabled: Boolean = false,
|
||||
checked: Boolean = false,
|
||||
onCheckedChange: (Boolean) -> Unit = {},
|
||||
multiSelectEnabled: Boolean = false,
|
||||
selected: Boolean = false,
|
||||
menuController: DropdownMenus.MenuController,
|
||||
onForwardClick: (InstalledStickerPack) -> Unit = {},
|
||||
onRemoveClick: (InstalledStickerPack) -> Unit = {},
|
||||
onSelectClick: (InstalledStickerPack) -> Unit = {},
|
||||
onSelectionToggle: (InstalledStickerPack) -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(12.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.background(
|
||||
color = if (selected) SignalTheme.colors.colorSurface2 else MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(18.dp)
|
||||
)
|
||||
.padding(vertical = 10.dp)
|
||||
) {
|
||||
if (multiSelectModeEnabled) {
|
||||
AnimatedVisibility(
|
||||
visible = multiSelectEnabled,
|
||||
enter = fadeIn() + expandHorizontally(),
|
||||
exit = fadeOut() + shrinkHorizontally()
|
||||
) {
|
||||
Checkbox(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
checked = selected,
|
||||
onCheckedChange = { onSelectionToggle(pack) },
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
}
|
||||
@@ -191,7 +208,7 @@ fun InstalledStickerPackRow(
|
||||
icon = ImageVector.vectorResource(R.drawable.symbol_check_circle_24),
|
||||
text = stringResource(R.string.StickerManagement_menu_select_pack),
|
||||
onClick = {
|
||||
onSelectClick(pack)
|
||||
onSelectionToggle(pack)
|
||||
menuController.hide()
|
||||
}
|
||||
)
|
||||
@@ -324,7 +341,7 @@ private fun AvailableStickerPackRowPreviewDownloaded() = SignalTheme {
|
||||
@Composable
|
||||
private fun InstalledStickerPackRowPreview() = SignalTheme {
|
||||
InstalledStickerPackRow(
|
||||
multiSelectModeEnabled = false,
|
||||
multiSelectEnabled = false,
|
||||
menuController = DropdownMenus.MenuController(),
|
||||
pack = StickerPreviewDataFactory.installedPack(
|
||||
title = "Bandit the Cat",
|
||||
@@ -338,7 +355,7 @@ private fun InstalledStickerPackRowPreview() = SignalTheme {
|
||||
@Composable
|
||||
private fun InstalledStickerPackRowSelectModePreview() = SignalTheme {
|
||||
InstalledStickerPackRow(
|
||||
multiSelectModeEnabled = true,
|
||||
multiSelectEnabled = true,
|
||||
menuController = DropdownMenus.MenuController(),
|
||||
pack = StickerPreviewDataFactory.installedPack(
|
||||
title = "Bandit the Cat",
|
||||
|
||||
Reference in New Issue
Block a user