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) ) } }