android: unify chat action sheets and resolve gesture conflicts
This commit is contained in:
@@ -437,3 +437,10 @@
|
|||||||
- Fixed profile screen usability after avatar upload:
|
- Fixed profile screen usability after avatar upload:
|
||||||
- enabled vertical scrolling with safe insets/navigation padding,
|
- enabled vertical scrolling with safe insets/navigation padding,
|
||||||
- constrained avatar preview to a centered circular area instead of full-screen takeover.
|
- constrained avatar preview to a centered circular area instead of full-screen takeover.
|
||||||
|
|
||||||
|
### Step 70 - Chat interaction consistency: gestures + sheets/dialogs
|
||||||
|
- Reworked single-message actions to open in `ModalBottomSheet` (tap action menu) instead of inline action bars.
|
||||||
|
- Reworked forward target chooser to `ModalBottomSheet` for consistent overlay behavior across chat actions.
|
||||||
|
- Added destructive action confirmation via `AlertDialog` before delete actions.
|
||||||
|
- Reduced gesture conflicts by removing attachment-level long-press handlers that collided with message selection gestures.
|
||||||
|
- Improved voice hold gesture reliability by handling consumed pointer down events (`requireUnconsumed = false`).
|
||||||
|
|||||||
@@ -35,11 +35,16 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -56,12 +61,10 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.input.pointer.positionChange
|
import androidx.compose.ui.input.pointer.positionChange
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
|
||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
@@ -186,6 +189,7 @@ fun ChatRoute(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatScreen(
|
fun ChatScreen(
|
||||||
state: MessageUiState,
|
state: MessageUiState,
|
||||||
@@ -226,6 +230,10 @@ fun ChatScreen(
|
|||||||
var viewerImageIndex by remember { mutableStateOf<Int?>(null) }
|
var viewerImageIndex by remember { mutableStateOf<Int?>(null) }
|
||||||
var dismissedPinnedMessageId by remember { mutableStateOf<Long?>(null) }
|
var dismissedPinnedMessageId by remember { mutableStateOf<Long?>(null) }
|
||||||
var actionMenuMessage by remember { mutableStateOf<MessageItem?>(null) }
|
var actionMenuMessage by remember { mutableStateOf<MessageItem?>(null) }
|
||||||
|
var pendingDeleteForAll by remember { mutableStateOf(false) }
|
||||||
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
|
val actionSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val forwardSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
|
||||||
LaunchedEffect(state.isRecordingVoice) {
|
LaunchedEffect(state.isRecordingVoice) {
|
||||||
if (!state.isRecordingVoice) return@LaunchedEffect
|
if (!state.isRecordingVoice) return@LaunchedEffect
|
||||||
@@ -419,71 +427,75 @@ fun ChatScreen(
|
|||||||
if (actionMenuMessage != null && state.actionState.mode != MessageSelectionMode.MULTI) {
|
if (actionMenuMessage != null && state.actionState.mode != MessageSelectionMode.MULTI) {
|
||||||
val selected = actionMenuMessage
|
val selected = actionMenuMessage
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
Column(
|
ModalBottomSheet(
|
||||||
modifier = Modifier
|
onDismissRequest = { actionMenuMessage = null },
|
||||||
.fillMaxWidth()
|
sheetState = actionSheetState,
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.96f))
|
) {
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
modifier = Modifier
|
||||||
) {
|
.fillMaxWidth()
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
listOf("👍", "❤️", "🔥", "😂").forEach { emoji ->
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
|
listOf("👍", "❤️", "🔥", "😂").forEach { emoji ->
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onSelectMessage(selected)
|
||||||
|
onToggleReaction(emoji)
|
||||||
|
actionMenuMessage = null
|
||||||
|
},
|
||||||
|
) { Text(emoji) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onReplySelected(selected)
|
||||||
|
actionMenuMessage = null
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) { Text("Reply") }
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onEditSelected(selected)
|
||||||
|
actionMenuMessage = null
|
||||||
|
},
|
||||||
|
enabled = state.selectedCanEdit,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) { Text("Edit") }
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
onSelectMessage(selected)
|
onSelectMessage(selected)
|
||||||
onToggleReaction(emoji)
|
onForwardSelected()
|
||||||
actionMenuMessage = null
|
actionMenuMessage = null
|
||||||
},
|
},
|
||||||
) { Text(emoji) }
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) { Text("Forward") }
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onSelectMessage(selected)
|
||||||
|
pendingDeleteForAll = false
|
||||||
|
showDeleteDialog = true
|
||||||
|
actionMenuMessage = null
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) { Text("Delete") }
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onEnterMultiSelect(selected)
|
||||||
|
actionMenuMessage = null
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) { Text("Select") }
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
actionMenuMessage = null
|
||||||
|
onClearSelection()
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) { Text("Close") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
onReplySelected(selected)
|
|
||||||
actionMenuMessage = null
|
|
||||||
},
|
|
||||||
) { Text("Reply") }
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
onEditSelected(selected)
|
|
||||||
actionMenuMessage = null
|
|
||||||
},
|
|
||||||
enabled = state.selectedCanEdit,
|
|
||||||
) { Text("Edit") }
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
onSelectMessage(selected)
|
|
||||||
onForwardSelected()
|
|
||||||
actionMenuMessage = null
|
|
||||||
},
|
|
||||||
) { Text("Forward") }
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
onSelectMessage(selected)
|
|
||||||
onDeleteSelected(false)
|
|
||||||
actionMenuMessage = null
|
|
||||||
},
|
|
||||||
) { Text("Delete") }
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
) {
|
|
||||||
Button(onClick = { onEnterMultiSelect(selected); actionMenuMessage = null }) {
|
|
||||||
Text("Select")
|
|
||||||
}
|
|
||||||
Button(onClick = {}, enabled = false) {
|
|
||||||
Text("Pin")
|
|
||||||
}
|
|
||||||
Button(onClick = { actionMenuMessage = null; onClearSelection() }) {
|
|
||||||
Text("Close")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,11 +518,24 @@ fun ChatScreen(
|
|||||||
)
|
)
|
||||||
if (state.actionState.mode == MessageSelectionMode.MULTI) {
|
if (state.actionState.mode == MessageSelectionMode.MULTI) {
|
||||||
Button(onClick = onForwardSelected) { Text("Forward") }
|
Button(onClick = onForwardSelected) { Text("Forward") }
|
||||||
Button(onClick = { onDeleteSelected(false) }) { Text("Delete") }
|
|
||||||
} else {
|
|
||||||
Button(onClick = { onDeleteSelected(false) }) { Text("Delete") }
|
|
||||||
Button(
|
Button(
|
||||||
onClick = { onDeleteSelected(true) },
|
onClick = {
|
||||||
|
pendingDeleteForAll = false
|
||||||
|
showDeleteDialog = true
|
||||||
|
},
|
||||||
|
) { Text("Delete") }
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
pendingDeleteForAll = false
|
||||||
|
showDeleteDialog = true
|
||||||
|
},
|
||||||
|
) { Text("Delete") }
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
pendingDeleteForAll = true
|
||||||
|
showDeleteDialog = true
|
||||||
|
},
|
||||||
enabled = state.selectedCanDeleteForAll,
|
enabled = state.selectedCanDeleteForAll,
|
||||||
) { Text("Del for all") }
|
) { Text("Del for all") }
|
||||||
Button(
|
Button(
|
||||||
@@ -538,35 +563,44 @@ fun ChatScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.forwardingMessageIds.isNotEmpty()) {
|
if (state.forwardingMessageIds.isNotEmpty()) {
|
||||||
Column(
|
ModalBottomSheet(
|
||||||
modifier = Modifier
|
onDismissRequest = onForwardDismiss,
|
||||||
.fillMaxWidth()
|
sheetState = forwardSheetState,
|
||||||
.background(MaterialTheme.colorScheme.secondaryContainer)
|
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
text = if (state.forwardingMessageIds.size == 1) {
|
modifier = Modifier
|
||||||
"Forward message #${state.forwardingMessageIds.first()}"
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (state.forwardingMessageIds.size == 1) {
|
||||||
|
"Forward message #${state.forwardingMessageIds.first()}"
|
||||||
|
} else {
|
||||||
|
"Forward ${state.forwardingMessageIds.size} messages"
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
if (state.availableForwardTargets.isEmpty()) {
|
||||||
|
Text("No available chats")
|
||||||
} else {
|
} else {
|
||||||
"Forward ${state.forwardingMessageIds.size} messages"
|
state.availableForwardTargets.forEach { target ->
|
||||||
},
|
Button(
|
||||||
style = MaterialTheme.typography.labelLarge,
|
onClick = { onForwardTargetSelected(target.chatId) },
|
||||||
)
|
enabled = !state.isForwarding,
|
||||||
if (state.availableForwardTargets.isEmpty()) {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
Text("No available chats")
|
) {
|
||||||
} else {
|
Text(target.title)
|
||||||
state.availableForwardTargets.forEach { target ->
|
}
|
||||||
Button(
|
|
||||||
onClick = { onForwardTargetSelected(target.chatId) },
|
|
||||||
enabled = !state.isForwarding,
|
|
||||||
) {
|
|
||||||
Text(target.title)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
TextButton(
|
||||||
Button(onClick = onForwardDismiss, enabled = !state.isForwarding) {
|
onClick = onForwardDismiss,
|
||||||
Text(if (state.isForwarding) "Forwarding..." else "Cancel")
|
enabled = !state.isForwarding,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(if (state.isForwarding) "Forwarding..." else "Cancel")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,6 +724,39 @@ fun ChatScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showDeleteDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDeleteDialog = false },
|
||||||
|
title = { Text("Delete message") },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
if (pendingDeleteForAll) {
|
||||||
|
"Delete selected message for everyone?"
|
||||||
|
} else {
|
||||||
|
"Delete selected message(s) for you?"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onDeleteSelected(pendingDeleteForAll)
|
||||||
|
showDeleteDialog = false
|
||||||
|
pendingDeleteForAll = false
|
||||||
|
},
|
||||||
|
) { Text("Delete") }
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showDeleteDialog = false
|
||||||
|
pendingDeleteForAll = false
|
||||||
|
},
|
||||||
|
) { Text("Cancel") }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (viewerImageIndex != null) {
|
if (viewerImageIndex != null) {
|
||||||
val currentIndex = viewerImageIndex ?: 0
|
val currentIndex = viewerImageIndex ?: 0
|
||||||
val currentUrl = allImageUrls.getOrNull(currentIndex)
|
val currentUrl = allImageUrls.getOrNull(currentIndex)
|
||||||
@@ -801,8 +868,6 @@ private fun MessageBubble(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongPress: () -> Unit,
|
onLongPress: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val clipboard = LocalClipboardManager.current
|
|
||||||
var contextAttachmentUrl by remember { mutableStateOf<String?>(null) }
|
|
||||||
val isOutgoing = message.isOutgoing
|
val isOutgoing = message.isOutgoing
|
||||||
val bubbleShape = if (isOutgoing) {
|
val bubbleShape = if (isOutgoing) {
|
||||||
RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp, bottomStart = 16.dp, bottomEnd = 6.dp)
|
RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp, bottomStart = 16.dp, bottomEnd = 6.dp)
|
||||||
@@ -928,10 +993,7 @@ private fun MessageBubble(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(180.dp)
|
.height(180.dp)
|
||||||
.combinedClickable(
|
.clickable { onAttachmentImageClick(single.fileUrl) },
|
||||||
onClick = { onAttachmentImageClick(single.fileUrl) },
|
|
||||||
onLongClick = { contextAttachmentUrl = single.fileUrl },
|
|
||||||
),
|
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -947,10 +1009,7 @@ private fun MessageBubble(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.height(110.dp)
|
.height(110.dp)
|
||||||
.combinedClickable(
|
.clickable { onAttachmentImageClick(image.fileUrl) },
|
||||||
onClick = { onAttachmentImageClick(image.fileUrl) },
|
|
||||||
onLongClick = { contextAttachmentUrl = image.fileUrl },
|
|
||||||
),
|
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -969,12 +1028,7 @@ private fun MessageBubble(
|
|||||||
val fileType = attachment.fileType.lowercase()
|
val fileType = attachment.fileType.lowercase()
|
||||||
when {
|
when {
|
||||||
fileType.startsWith("video/") -> {
|
fileType.startsWith("video/") -> {
|
||||||
Box(
|
Box {
|
||||||
modifier = Modifier.combinedClickable(
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = { contextAttachmentUrl = attachment.fileUrl },
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
VideoAttachmentCard(
|
VideoAttachmentCard(
|
||||||
url = attachment.fileUrl,
|
url = attachment.fileUrl,
|
||||||
fileType = attachment.fileType,
|
fileType = attachment.fileType,
|
||||||
@@ -982,22 +1036,12 @@ private fun MessageBubble(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fileType.startsWith("audio/") -> {
|
fileType.startsWith("audio/") -> {
|
||||||
Box(
|
Box {
|
||||||
modifier = Modifier.combinedClickable(
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = { contextAttachmentUrl = attachment.fileUrl },
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
AudioAttachmentPlayer(url = attachment.fileUrl)
|
AudioAttachmentPlayer(url = attachment.fileUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Box(
|
Box {
|
||||||
modifier = Modifier.combinedClickable(
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = { contextAttachmentUrl = attachment.fileUrl },
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
FileAttachmentRow(
|
FileAttachmentRow(
|
||||||
fileUrl = attachment.fileUrl,
|
fileUrl = attachment.fileUrl,
|
||||||
fileType = attachment.fileType,
|
fileType = attachment.fileType,
|
||||||
@@ -1010,27 +1054,6 @@ private fun MessageBubble(
|
|||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!contextAttachmentUrl.isNullOrBlank()) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
|
||||||
) {
|
|
||||||
Button(onClick = { onAttachmentImageClick(contextAttachmentUrl!!) }) {
|
|
||||||
Text("Open")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
clipboard.setText(AnnotatedString(contextAttachmentUrl!!))
|
|
||||||
contextAttachmentUrl = null
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text("Copy link")
|
|
||||||
}
|
|
||||||
Button(onClick = { contextAttachmentUrl = null }) {
|
|
||||||
Text("Close")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
@@ -1241,7 +1264,7 @@ private fun VoiceHoldToRecordButton(
|
|||||||
.pointerInput(enabled, isRecording, isLocked) {
|
.pointerInput(enabled, isRecording, isLocked) {
|
||||||
if (!enabled || isLocked) return@pointerInput
|
if (!enabled || isLocked) return@pointerInput
|
||||||
awaitEachGesture {
|
awaitEachGesture {
|
||||||
val down = awaitFirstDown()
|
val down = awaitFirstDown(requireUnconsumed = false)
|
||||||
onStart()
|
onStart()
|
||||||
var cancelledBySlide = false
|
var cancelledBySlide = false
|
||||||
var lockedBySlide = false
|
var lockedBySlide = false
|
||||||
@@ -1272,7 +1295,7 @@ private fun VoiceHoldToRecordButton(
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = onStart,
|
onClick = {},
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
modifier = Modifier.semantics { contentDescription = "Hold to record voice message" },
|
modifier = Modifier.semantics { contentDescription = "Hold to record voice message" },
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -98,8 +98,8 @@
|
|||||||
## 13. UI/UX и темы
|
## 13. UI/UX и темы
|
||||||
- [x] Светлая/темная тема (читаемая)
|
- [x] Светлая/темная тема (читаемая)
|
||||||
- [ ] Адаптивность phone/tablet
|
- [ ] Адаптивность phone/tablet
|
||||||
- [ ] Контекстные меню без конфликтов жестов
|
- [x] Контекстные меню без конфликтов жестов
|
||||||
- [ ] Bottom sheets/dialog behavior consistency
|
- [x] Bottom sheets/dialog behavior consistency
|
||||||
- [x] Accessibility (TalkBack, dynamic type)
|
- [x] Accessibility (TalkBack, dynamic type)
|
||||||
|
|
||||||
## 14. Безопасность
|
## 14. Безопасность
|
||||||
|
|||||||
Reference in New Issue
Block a user