fix: LOCKED panel 1:1 с Telegram — полностью другой layout при lock
Telegram при LOCKED: таймер и dot СКРЫТЫ, вместо них: - [Delete 44dp] — красная иконка удаления слева - [Waveform] — заполняет оставшееся место - Lock→Pause кнопка наверху (отдельный overlay) - Circle = Send (без blob) При RECORDING (без изменений): - [dot][timer] [◀ Slide to cancel] [Circle+Blob] Реализация: AnimatedContent crossfade между двумя полностью разными panel layouts. RecordLockedControls больше не используется в панели — delete в самой панели, pause в LockIcon overlay. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2095,9 +2095,11 @@ fun MessageInputBar(
|
|||||||
label = "record_ui_shift"
|
label = "record_ui_shift"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── Telegram-style recording layout ──
|
// ── Telegram-exact recording layout ──
|
||||||
// Telegram uses separate overlay layers (RecordCircle 194dp, ControlsView 250dp)
|
// RECORDING: [dot][timer] [◀ Slide to cancel] ... [Circle+Blob]
|
||||||
// We replicate with Box layers + graphicsLayer for overflow
|
// LOCKED: [Delete] [Waveform 32dp] ... [Circle=Send] + Lock→Pause above
|
||||||
|
// PAUSED: [Delete] [Waveform 32dp] ... [Circle=Send] + Lock→Play above
|
||||||
|
// Timer and dot are HIDDEN in LOCKED/PAUSED (Telegram exact)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -2108,93 +2110,103 @@ fun MessageInputBar(
|
|||||||
recordingInputRowY = coordinates.positionInWindow().y
|
recordingInputRowY = coordinates.positionInWindow().y
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
// ── Layer 1: Panel bar (timer + center) ──
|
val isLockedOrPaused = recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED
|
||||||
// Telegram: full-width bar, circle overlaps right edge
|
|
||||||
Row(
|
// Crossfade between RECORDING panel and LOCKED panel
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = isLockedOrPaused,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(40.dp)
|
.height(44.dp)
|
||||||
.clip(RoundedCornerShape(20.dp))
|
.padding(end = 52.dp), // space for circle overlay
|
||||||
.background(recordingPanelColor)
|
transitionSpec = {
|
||||||
.padding(start = 13.dp, end = 52.dp), // 52dp = half-circle overlap
|
fadeIn(tween(200)) togetherWith fadeOut(tween(200))
|
||||||
verticalAlignment = Alignment.CenterVertically
|
},
|
||||||
) {
|
label = "record_panel_mode"
|
||||||
// Blink dot + timer
|
) { locked ->
|
||||||
Row(
|
if (locked) {
|
||||||
modifier = Modifier
|
// ── LOCKED/PAUSED panel (Telegram: recordedAudioPanel) ──
|
||||||
.graphicsLayer {
|
// [Delete 44dp] [Waveform fills rest]
|
||||||
alpha = recordUiAlpha
|
Row(
|
||||||
translationX = with(density) { recordUiShift.toPx() }
|
modifier = Modifier
|
||||||
},
|
.fillMaxSize()
|
||||||
verticalAlignment = Alignment.CenterVertically
|
.clip(RoundedCornerShape(22.dp))
|
||||||
) {
|
.background(recordingPanelColor),
|
||||||
if (recordUiState == RecordUiState.PAUSED) {
|
verticalAlignment = Alignment.CenterVertically
|
||||||
Box(modifier = Modifier.size(28.dp), contentAlignment = Alignment.Center) {
|
) {
|
||||||
Box(modifier = Modifier.size(10.dp).clip(CircleShape)
|
// Delete button — Telegram: 44×44dp, Lottie trash icon
|
||||||
.background(if (isDarkTheme) Color(0xFFFF5A5A) else Color(0xFFE84D4D)))
|
Box(
|
||||||
}
|
modifier = Modifier
|
||||||
} else {
|
.size(44.dp)
|
||||||
RecordBlinkDot(isDarkTheme = isDarkTheme)
|
.clickable(
|
||||||
}
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
Spacer(modifier = Modifier.width(6.dp))
|
indication = null
|
||||||
Text(
|
) {
|
||||||
text = formatVoiceRecordTimer(voiceElapsedMs),
|
|
||||||
color = recordingTextColor,
|
|
||||||
fontSize = 15.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
|
|
||||||
// Center: SlideToCancel or Waveform+Controls
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.graphicsLayer {
|
|
||||||
alpha = recordUiAlpha
|
|
||||||
translationX = with(density) { recordUiShift.toPx() }
|
|
||||||
},
|
|
||||||
transitionSpec = {
|
|
||||||
fadeIn(tween(200)) togetherWith fadeOut(tween(200))
|
|
||||||
},
|
|
||||||
label = "record_center_content"
|
|
||||||
) { isLockedOrPaused ->
|
|
||||||
if (isLockedOrPaused) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
VoiceWaveformBar(
|
|
||||||
waves = voiceWaves,
|
|
||||||
isDarkTheme = isDarkTheme,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
RecordLockedControls(
|
|
||||||
isPaused = recordUiState == RecordUiState.PAUSED,
|
|
||||||
isDarkTheme = isDarkTheme,
|
|
||||||
onDelete = {
|
|
||||||
inputJumpLog("tap DELETE (locked/paused) mode=$recordMode state=$recordUiState")
|
inputJumpLog("tap DELETE (locked/paused) mode=$recordMode state=$recordUiState")
|
||||||
stopVoiceRecording(send = false)
|
stopVoiceRecording(send = false)
|
||||||
},
|
},
|
||||||
onTogglePause = {
|
contentAlignment = Alignment.Center
|
||||||
inputJumpLog("tap PAUSE/RESUME mode=$recordMode state=$recordUiState")
|
) {
|
||||||
if (recordUiState == RecordUiState.PAUSED) {
|
Icon(
|
||||||
resumeVoiceRecording()
|
imageVector = Icons.Default.Close,
|
||||||
} else {
|
contentDescription = "Delete recording",
|
||||||
pauseVoiceRecording()
|
tint = if (isDarkTheme) Color(0xFFFF5A5A) else Color(0xFFE84D4D),
|
||||||
}
|
modifier = Modifier.size(20.dp)
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
// Waveform — Telegram: 32dp height, fills remaining width
|
||||||
|
VoiceWaveformBar(
|
||||||
|
waves = voiceWaves,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ── RECORDING panel ──
|
||||||
|
// [dot][timer] [◀ Slide to cancel]
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clip(RoundedCornerShape(22.dp))
|
||||||
|
.background(recordingPanelColor)
|
||||||
|
.padding(start = 13.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Blink dot + timer
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = recordUiAlpha
|
||||||
|
translationX = with(density) { recordUiShift.toPx() }
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RecordBlinkDot(isDarkTheme = isDarkTheme)
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text(
|
||||||
|
text = formatVoiceRecordTimer(voiceElapsedMs),
|
||||||
|
color = recordingTextColor,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
// Slide to cancel
|
||||||
SlideToCancel(
|
SlideToCancel(
|
||||||
slideDx = slideDx,
|
slideDx = slideDx,
|
||||||
cancelThresholdPx = cancelDragThresholdPx,
|
cancelThresholdPx = cancelDragThresholdPx,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = recordUiAlpha
|
||||||
|
translationX = with(density) { recordUiShift.toPx() }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user