From b13cdb7ea132186df4571947403ccc1b091f75f9 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Sat, 11 Apr 2026 21:41:29 +0500 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D1=82=D1=8C=20layout=20=D0=B7=D0=B0=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B8=20=E2=80=94=20layered=20=D0=B0=D1=80=D1=85=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=B2=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=20cramming=20=D0=B2=2040dp=20panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- .../ui/chats/input/ChatDetailInput.kt | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt index 693a113..e6b9259 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt @@ -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) ) } }