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 e6b9259..7f2aed8 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 @@ -1956,7 +1956,9 @@ fun MessageInputBar( label = "record_ui_shift" ) - // Outer Box — no clip, allows children to overflow + // ── Telegram-style recording layout ── + // Telegram uses separate overlay layers (RecordCircle 194dp, ControlsView 250dp) + // We replicate with Box layers + graphicsLayer for overflow Box( modifier = Modifier .fillMaxWidth() @@ -1967,20 +1969,20 @@ fun MessageInputBar( recordingInputRowY = coordinates.positionInWindow().y } ) { - // ── Layer 1: Panel bar ── - Box( + // ── Layer 1: Panel bar (timer + center) ── + // Telegram: full-width bar, circle overlaps right edge + Row( modifier = Modifier .fillMaxWidth() .height(40.dp) - .padding(end = 44.dp) // space for circle overlap .clip(RoundedCornerShape(20.dp)) .background(recordingPanelColor) - .padding(horizontal = 13.dp) + .padding(start = 13.dp, end = 52.dp), // 52dp = half-circle overlap + verticalAlignment = Alignment.CenterVertically ) { - // Left: blink dot + timer + // Blink dot + timer Row( modifier = Modifier - .align(Alignment.CenterStart) .graphicsLayer { alpha = recordUiAlpha translationX = with(density) { recordUiShift.toPx() } @@ -1988,18 +1990,9 @@ fun MessageInputBar( verticalAlignment = Alignment.CenterVertically ) { if (recordUiState == RecordUiState.PAUSED) { - Box( - modifier = Modifier.size(28.dp), - contentAlignment = Alignment.Center - ) { - Box( - modifier = Modifier - .size(10.dp) - .clip(CircleShape) - .background( - if (isDarkTheme) Color(0xFFFF5A5A) else Color(0xFFE84D4D) - ) - ) + Box(modifier = Modifier.size(28.dp), contentAlignment = Alignment.Center) { + Box(modifier = Modifier.size(10.dp).clip(CircleShape) + .background(if (isDarkTheme) Color(0xFFFF5A5A) else Color(0xFFE84D4D))) } } else { RecordBlinkDot(isDarkTheme = isDarkTheme) @@ -2013,11 +2006,13 @@ fun MessageInputBar( ) } - // Center content: SlideToCancel or Waveform+Controls + Spacer(modifier = Modifier.width(12.dp)) + + // Center: SlideToCancel or Waveform+Controls AnimatedContent( targetState = recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED, modifier = Modifier - .align(Alignment.Center) + .weight(1f) .graphicsLayer { alpha = recordUiAlpha translationX = with(density) { recordUiShift.toPx() } @@ -2066,22 +2061,24 @@ fun MessageInputBar( } } - // ── Layer 2: Mic/Send circle overlay ── - // Positioned at right edge, overlapping the panel + // ── Layer 2: Circle + Lock overlay ── + // 48dp layout box at right edge; visuals overflow via graphicsLayer + // Telegram: circle center at 26dp from right, radius 41dp = 82dp visual Box( modifier = Modifier .size(48.dp) .align(Alignment.CenterEnd) + .offset(x = 4.dp) // slight overlap into right padding .zIndex(5f), contentAlignment = Alignment.Center ) { - // ── Layer 3: LockIcon above circle ── + // Lock icon: floats ~70dp above circle center (Telegram: ~92dp) if (recordUiState == RecordUiState.RECORDING || recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED ) { - val lockSizeDp = (50.dp - 14.dp * lockProgress) - val lockYOffset = ((-56).dp + 14.dp * lockProgress) + val lockSizeDp = 50.dp - 14.dp * lockProgress + val lockYDp = -70.dp + 14.dp * lockProgress LockIcon( lockProgress = lockProgress, isLocked = recordUiState == RecordUiState.LOCKED, @@ -2090,7 +2087,7 @@ fun MessageInputBar( modifier = Modifier .size(lockSizeDp) .graphicsLayer { - translationY = with(density) { lockYOffset.toPx() } + translationY = with(density) { lockYDp.toPx() } clip = false } .zIndex(10f) @@ -2102,8 +2099,8 @@ fun MessageInputBar( isDarkTheme = isDarkTheme, modifier = Modifier .graphicsLayer { - translationX = with(density) { (-80).dp.toPx() } - translationY = with(density) { (-56).dp.toPx() } + translationX = with(density) { (-90).dp.toPx() } + translationY = with(density) { (-70).dp.toPx() } clip = false } .zIndex(11f) @@ -2111,38 +2108,37 @@ fun MessageInputBar( } } - // Blob animation (visual-only enlargement) + // Blob: 48dp base → 1.7x = ~82dp visual (matches Telegram circleRadius 41dp) VoiceButtonBlob( voiceLevel = voiceLevel, isDarkTheme = isDarkTheme, modifier = Modifier .size(48.dp) .graphicsLayer { - scaleX = 1.8f - scaleY = 1.8f + scaleX = 1.7f + scaleY = 1.7f clip = false } ) - // Send or Mic button + // Solid circle: 48dp layout, scaled to 82dp visual val sendScale by animateFloatAsState( targetValue = if (recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED) 1f else 0f, animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing), label = "send_btn_scale" ) + val circleScale = 1.71f // 48dp * 1.71 ≈ 82dp (Telegram) if (recordUiState == RecordUiState.LOCKED || recordUiState == RecordUiState.PAUSED) { Box( modifier = Modifier .size(48.dp) .graphicsLayer { - scaleX = sendScale - scaleY = sendScale - } - .shadow( - elevation = 6.dp, - shape = CircleShape, + scaleX = circleScale * sendScale + scaleY = circleScale * sendScale clip = false - ) + shadowElevation = 8f + shape = CircleShape + } .clip(CircleShape) .background(PrimaryBlue) .clickable( @@ -2161,13 +2157,18 @@ fun MessageInputBar( imageVector = TelegramSendIcon, contentDescription = "Send voice message", tint = Color.White, - modifier = Modifier.size(22.dp) + modifier = Modifier.size(24.dp) ) } } else { Box( modifier = Modifier .size(48.dp) + .graphicsLayer { + scaleX = circleScale + scaleY = circleScale + clip = false + } .clip(CircleShape) .background(PrimaryBlue), contentAlignment = Alignment.Center @@ -2176,7 +2177,7 @@ fun MessageInputBar( imageVector = if (recordMode == RecordMode.VOICE) Icons.Default.Mic else Icons.Default.Videocam, contentDescription = null, tint = Color.White, - modifier = Modifier.size(22.dp) + modifier = Modifier.size(24.dp) ) } }