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