Compare commits
2 Commits
aa3cc76646
...
7630aa6874
| Author | SHA1 | Date | |
|---|---|---|---|
| 7630aa6874 | |||
| afebbf6acb |
@@ -781,6 +781,17 @@ private fun VoiceWaveformBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Telegram-exact locked recording controls.
|
||||||
|
*
|
||||||
|
* Layout: [CANCEL text-button] [⏸/▶ circle button]
|
||||||
|
*
|
||||||
|
* - CANCEL = blue text (15sp bold, uppercase), clickable — cancels recording
|
||||||
|
* - ⏸ = small circle button (36dp), toggles pause/resume
|
||||||
|
* - No separate delete icon — CANCEL IS delete
|
||||||
|
*
|
||||||
|
* Reference: ChatActivityEnterView recordedAudioPanel + SlideTextView cancelToProgress
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun RecordLockedControls(
|
private fun RecordLockedControls(
|
||||||
isPaused: Boolean,
|
isPaused: Boolean,
|
||||||
@@ -789,37 +800,31 @@ private fun RecordLockedControls(
|
|||||||
onTogglePause: () -> Unit,
|
onTogglePause: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val deleteBgColor = if (isDarkTheme) Color(0xFF444444) else Color(0xFFE0E0E0)
|
val cancelColor = if (isDarkTheme) Color(0xFF69CCFF) else Color(0xFF2D9CFF)
|
||||||
val deleteIconColor = if (isDarkTheme) Color.White.copy(alpha = 0.8f) else Color(0xFF666666)
|
val pauseBgColor = if (isDarkTheme) Color(0xFF69CCFF).copy(alpha = 0.15f) else Color(0xFF2D9CFF).copy(alpha = 0.1f)
|
||||||
val pauseBgColor = if (isDarkTheme) Color(0xFF69CCFF).copy(alpha = 0.3f) else Color(0xFF2D9CFF).copy(alpha = 0.2f)
|
|
||||||
val pauseIconColor = if (isDarkTheme) Color(0xFF69CCFF) else Color(0xFF2D9CFF)
|
val pauseIconColor = if (isDarkTheme) Color(0xFF69CCFF) else Color(0xFF2D9CFF)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
// Delete button
|
// CANCEL text button — Telegram: blue bold uppercase
|
||||||
Box(
|
Text(
|
||||||
|
text = "CANCEL",
|
||||||
|
color = cancelColor,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 1,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(36.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(deleteBgColor)
|
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = null
|
indication = null
|
||||||
) { onDelete() },
|
) { onDelete() }
|
||||||
contentAlignment = Alignment.Center
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
) {
|
)
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Close,
|
|
||||||
contentDescription = "Delete recording",
|
|
||||||
tint = deleteIconColor,
|
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause/Resume button
|
// Pause/Resume button — circle with icon
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(36.dp)
|
.size(36.dp)
|
||||||
@@ -832,7 +837,8 @@ private fun RecordLockedControls(
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
if (isPaused) {
|
if (isPaused) {
|
||||||
Canvas(modifier = Modifier.size(12.dp)) {
|
// Play triangle
|
||||||
|
Canvas(modifier = Modifier.size(14.dp)) {
|
||||||
val path = Path().apply {
|
val path = Path().apply {
|
||||||
moveTo(size.width * 0.2f, 0f)
|
moveTo(size.width * 0.2f, 0f)
|
||||||
lineTo(size.width, size.height / 2f)
|
lineTo(size.width, size.height / 2f)
|
||||||
@@ -842,19 +848,20 @@ private fun RecordLockedControls(
|
|||||||
drawPath(path, color = pauseIconColor)
|
drawPath(path, color = pauseIconColor)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Canvas(modifier = Modifier.size(12.dp)) {
|
// Pause bars
|
||||||
val barW = size.width * 0.25f
|
Canvas(modifier = Modifier.size(14.dp)) {
|
||||||
val gap = size.width * 0.15f
|
val barW = size.width * 0.22f
|
||||||
|
val gap = size.width * 0.14f
|
||||||
drawRoundRect(
|
drawRoundRect(
|
||||||
color = pauseIconColor,
|
color = pauseIconColor,
|
||||||
topLeft = Offset(size.width / 2f - gap - barW, 0f),
|
topLeft = Offset(size.width / 2f - gap - barW, size.height * 0.1f),
|
||||||
size = androidx.compose.ui.geometry.Size(barW, size.height),
|
size = androidx.compose.ui.geometry.Size(barW, size.height * 0.8f),
|
||||||
cornerRadius = androidx.compose.ui.geometry.CornerRadius(barW / 3f)
|
cornerRadius = androidx.compose.ui.geometry.CornerRadius(barW / 3f)
|
||||||
)
|
)
|
||||||
drawRoundRect(
|
drawRoundRect(
|
||||||
color = pauseIconColor,
|
color = pauseIconColor,
|
||||||
topLeft = Offset(size.width / 2f + gap, 0f),
|
topLeft = Offset(size.width / 2f + gap, size.height * 0.1f),
|
||||||
size = androidx.compose.ui.geometry.Size(barW, size.height),
|
size = androidx.compose.ui.geometry.Size(barW, size.height * 0.8f),
|
||||||
cornerRadius = androidx.compose.ui.geometry.CornerRadius(barW / 3f)
|
cornerRadius = androidx.compose.ui.geometry.CornerRadius(barW / 3f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2240,18 +2247,20 @@ fun MessageInputBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blob: 48dp base → 1.7x = ~82dp visual (matches Telegram circleRadius 41dp)
|
// Blob: only during RECORDING (Telegram hides waves when locked)
|
||||||
VoiceButtonBlob(
|
if (recordUiState == RecordUiState.RECORDING) {
|
||||||
voiceLevel = voiceLevel,
|
VoiceButtonBlob(
|
||||||
isDarkTheme = isDarkTheme,
|
voiceLevel = voiceLevel,
|
||||||
modifier = Modifier
|
isDarkTheme = isDarkTheme,
|
||||||
.size(48.dp)
|
modifier = Modifier
|
||||||
.graphicsLayer {
|
.size(48.dp)
|
||||||
scaleX = 1.7f
|
.graphicsLayer {
|
||||||
scaleY = 1.7f
|
scaleX = 1.7f
|
||||||
clip = false
|
scaleY = 1.7f
|
||||||
}
|
clip = false
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Solid circle: 48dp layout, scaled to 82dp visual
|
// Solid circle: 48dp layout, scaled to 82dp visual
|
||||||
val sendScale by animateFloatAsState(
|
val sendScale by animateFloatAsState(
|
||||||
@@ -2513,33 +2522,32 @@ fun MessageInputBar(
|
|||||||
val absDy = kotlin.math.abs(dy)
|
val absDy = kotlin.math.abs(dy)
|
||||||
if (absDx > maxAbsDx) maxAbsDx = absDx
|
if (absDx > maxAbsDx) maxAbsDx = absDx
|
||||||
if (absDy > maxAbsDy) maxAbsDy = absDy
|
if (absDy > maxAbsDy) maxAbsDy = absDy
|
||||||
} else if (
|
} else if (recordUiState == RecordUiState.RECORDING) {
|
||||||
recordUiState == RecordUiState.RECORDING ||
|
// Only RECORDING processes slide gestures
|
||||||
recordUiState == RecordUiState.LOCKED
|
// LOCKED/PAUSED: no gesture processing (Telegram: return false)
|
||||||
) {
|
|
||||||
val dx = change.position.x - pressStartX
|
val dx = change.position.x - pressStartX
|
||||||
val dy = change.position.y - pressStartY
|
val dy = change.position.y - pressStartY
|
||||||
slideDx = dx
|
slideDx = dx
|
||||||
slideDy = dy
|
slideDy = dy
|
||||||
|
lockProgress = ((-dy) / lockDragThresholdPx).coerceIn(0f, 1f)
|
||||||
|
|
||||||
if (recordUiState == RecordUiState.RECORDING) {
|
if (dx <= -cancelDragThresholdPx) {
|
||||||
lockProgress = ((-dy) / lockDragThresholdPx).coerceIn(0f, 1f)
|
inputJumpLog(
|
||||||
if (dx <= -cancelDragThresholdPx) {
|
"gesture CANCEL dx=${dx.toInt()} threshold=${cancelDragThresholdPx.toInt()} mode=$recordMode"
|
||||||
inputJumpLog(
|
)
|
||||||
"gesture CANCEL dx=${dx.toInt()} threshold=${cancelDragThresholdPx.toInt()} mode=$recordMode"
|
stopVoiceRecording(send = false)
|
||||||
)
|
setRecordUiState(RecordUiState.IDLE, "slide-cancel")
|
||||||
stopVoiceRecording(send = false)
|
resetGestureState()
|
||||||
setRecordUiState(RecordUiState.IDLE, "slide-cancel")
|
finished = true
|
||||||
resetGestureState()
|
} else if (dy <= -lockDragThresholdPx) {
|
||||||
finished = true
|
view.performHapticFeedback(android.view.HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
} else if (dy <= -lockDragThresholdPx) {
|
lockProgress = 1f
|
||||||
view.performHapticFeedback(android.view.HapticFeedbackConstants.KEYBOARD_TAP)
|
slideDx = 0f // reset horizontal slide on lock
|
||||||
lockProgress = 1f
|
slideDy = 0f
|
||||||
setRecordUiState(
|
setRecordUiState(
|
||||||
RecordUiState.LOCKED,
|
RecordUiState.LOCKED,
|
||||||
"slide-lock dy=${dy.toInt()}"
|
"slide-lock dy=${dy.toInt()}"
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
change.consume()
|
change.consume()
|
||||||
|
|||||||
Reference in New Issue
Block a user