fix: переделать layout записи — layered архитектура вместо cramming в 40dp panel
- Panel bar (timer + slide-to-cancel/waveform) как Layer 1 - Mic/Send circle (48dp) как overlay Layer 2 поверх панели - LockIcon как Layer 3 над кругом через graphicsLayer (без clip) - Убран padding(end=94dp), заменён на padding(end=44dp) - Убран offset(x=8dp) который толкал круг за экран - Controls увеличены 28dp→36dp для лучшей тач-зоны - Blob scale 2.05→1.8 пропорционально новому 48dp размеру Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -670,7 +670,7 @@ private fun RecordLockedControls(
|
||||
// Delete button
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(28.dp)
|
||||
.size(36.dp)
|
||||
.clip(CircleShape)
|
||||
.background(deleteBgColor)
|
||||
.clickable(
|
||||
@@ -683,14 +683,14 @@ private fun RecordLockedControls(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = "Delete recording",
|
||||
tint = deleteIconColor,
|
||||
modifier = Modifier.size(16.dp)
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Pause/Resume button
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(28.dp)
|
||||
.size(36.dp)
|
||||
.clip(CircleShape)
|
||||
.background(pauseBgColor)
|
||||
.clickable(
|
||||
@@ -1921,7 +1921,10 @@ fun MessageInputBar(
|
||||
}
|
||||
}
|
||||
|
||||
// Fix #8: AnimatedVisibility for smooth exit
|
||||
// ── Recording panel (layered architecture) ──
|
||||
// Layer 1: panel bar (timer + center content)
|
||||
// Layer 2: mic/send circle OVERLAY at right edge (extends beyond panel)
|
||||
// Layer 3: lock icon ABOVE circle (extends above panel)
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = isVoiceRecording,
|
||||
enter = fadeIn(tween(180)) + expandVertically(tween(180)),
|
||||
@@ -1953,26 +1956,28 @@ fun MessageInputBar(
|
||||
label = "record_ui_shift"
|
||||
)
|
||||
|
||||
// Outer Box — no clip, allows children to overflow
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 48.dp)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
recordingInputRowHeightPx = coordinates.size.height
|
||||
recordingInputRowY = coordinates.positionInWindow().y
|
||||
},
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
}
|
||||
) {
|
||||
// ── Layer 1: Panel bar ──
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp)
|
||||
.padding(end = 44.dp) // space for circle overlap
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.background(recordingPanelColor)
|
||||
.padding(start = 13.dp, end = 94.dp)
|
||||
.padding(horizontal = 13.dp)
|
||||
) {
|
||||
// Left: blink dot + timer (all states)
|
||||
// Left: blink dot + timer
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
@@ -1983,7 +1988,6 @@ fun MessageInputBar(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (recordUiState == RecordUiState.PAUSED) {
|
||||
// Static dot (no blink) when paused
|
||||
Box(
|
||||
modifier = Modifier.size(28.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
@@ -2009,7 +2013,7 @@ fun MessageInputBar(
|
||||
)
|
||||
}
|
||||
|
||||
// Center: SlideToCancel or Waveform+Controls
|
||||
// Center content: SlideToCancel or Waveform+Controls
|
||||
AnimatedContent(
|
||||
targetState = recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED,
|
||||
modifier = Modifier
|
||||
@@ -2062,22 +2066,22 @@ fun MessageInputBar(
|
||||
}
|
||||
}
|
||||
|
||||
// Mic button area with LockIcon overlay
|
||||
// ── Layer 2: Mic/Send circle overlay ──
|
||||
// Positioned at right edge, overlapping the panel
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.offset(x = 8.dp),
|
||||
.size(48.dp)
|
||||
.align(Alignment.CenterEnd)
|
||||
.zIndex(5f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
// LockIcon positioned above the mic button
|
||||
// ── Layer 3: LockIcon above circle ──
|
||||
if (recordUiState == RecordUiState.RECORDING ||
|
||||
recordUiState == RecordUiState.LOCKED ||
|
||||
recordUiState == RecordUiState.PAUSED
|
||||
) {
|
||||
// Fix #1: Lock grows 50dp→36dp as lockProgress 0→1
|
||||
// Fix #2: Y-position animates closer to mic
|
||||
val lockSizeDp = (50.dp - 14.dp * lockProgress)
|
||||
val lockYOffset = ((-60).dp + 14.dp * lockProgress)
|
||||
val lockYOffset = ((-56).dp + 14.dp * lockProgress)
|
||||
LockIcon(
|
||||
lockProgress = lockProgress,
|
||||
isLocked = recordUiState == RecordUiState.LOCKED,
|
||||
@@ -2085,35 +2089,42 @@ fun MessageInputBar(
|
||||
isDarkTheme = isDarkTheme,
|
||||
modifier = Modifier
|
||||
.size(lockSizeDp)
|
||||
.offset(y = lockYOffset)
|
||||
.graphicsLayer {
|
||||
translationY = with(density) { lockYOffset.toPx() }
|
||||
clip = false
|
||||
}
|
||||
.zIndex(10f)
|
||||
)
|
||||
|
||||
// Tooltip
|
||||
if (showLockTooltip && recordUiState == RecordUiState.RECORDING) {
|
||||
LockTooltip(
|
||||
visible = showLockTooltip,
|
||||
isDarkTheme = isDarkTheme,
|
||||
modifier = Modifier
|
||||
.offset(x = (-80).dp, y = (-60).dp)
|
||||
.graphicsLayer {
|
||||
translationX = with(density) { (-80).dp.toPx() }
|
||||
translationY = with(density) { (-56).dp.toPx() }
|
||||
clip = false
|
||||
}
|
||||
.zIndex(11f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Blob animation (visual-only enlargement)
|
||||
VoiceButtonBlob(
|
||||
voiceLevel = voiceLevel,
|
||||
isDarkTheme = isDarkTheme,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.size(48.dp)
|
||||
.graphicsLayer {
|
||||
scaleX = 2.05f
|
||||
scaleY = 2.05f
|
||||
scaleX = 1.8f
|
||||
scaleY = 1.8f
|
||||
clip = false
|
||||
}
|
||||
)
|
||||
|
||||
// Fix #7: Send button with scale animation
|
||||
// Send or Mic button
|
||||
val sendScale by animateFloatAsState(
|
||||
targetValue = if (recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED) 1f else 0f,
|
||||
animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing),
|
||||
@@ -2122,13 +2133,13 @@ fun MessageInputBar(
|
||||
if (recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(82.dp)
|
||||
.size(48.dp)
|
||||
.graphicsLayer {
|
||||
scaleX = sendScale
|
||||
scaleY = sendScale
|
||||
}
|
||||
.shadow(
|
||||
elevation = 10.dp,
|
||||
elevation = 6.dp,
|
||||
shape = CircleShape,
|
||||
clip = false
|
||||
)
|
||||
@@ -2150,22 +2161,22 @@ fun MessageInputBar(
|
||||
imageVector = TelegramSendIcon,
|
||||
contentDescription = "Send voice message",
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(30.dp)
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(82.dp)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(PrimaryBlue.copy(alpha = 0.92f)),
|
||||
.background(PrimaryBlue),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (recordMode == RecordMode.VOICE) Icons.Default.Mic else Icons.Default.Videocam,
|
||||
contentDescription = null,
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(30.dp)
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user