From 5145388e0242c8ae8d432ebdc4ea822297d104f8 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Wed, 21 Jan 2026 02:17:17 +0500 Subject: [PATCH] feat: Enhance ProfileScreen with improved collapsing header and back navigation --- .../messenger/ui/settings/ProfileScreen.kt | 238 +++++++++--------- 1 file changed, 112 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt index 30fae62..43f4e5b 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt @@ -3,10 +3,14 @@ package com.rosetta.messenger.ui.settings import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.util.Log import androidx.compose.animation.* import androidx.compose.animation.core.* import androidx.compose.foundation.* import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -27,6 +31,8 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.TextStyle @@ -41,6 +47,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.math.roundToInt +private const val TAG = "ProfileScreen" + // 🎨 Avatar colors - ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Ρ‚Π΅ ΠΆΠ΅ Ρ†Π²Π΅Ρ‚Π° Ρ‡Ρ‚ΠΎ ΠΈ Π² ChatsListScreen private val avatarColorsLight = listOf( @@ -103,7 +111,7 @@ private fun getInitials(name: String): String { // 🎯 COLLAPSING HEADER CONSTANTS // ═════════════════════════════════════════════════════════════ private val EXPANDED_HEADER_HEIGHT = 280.dp -private val COLLAPSED_HEADER_HEIGHT = 56.dp +private val COLLAPSED_HEADER_HEIGHT = 64.dp // Increased for more bottom space private val AVATAR_SIZE_EXPANDED = 120.dp private val AVATAR_SIZE_COLLAPSED = 36.dp private val STATUS_BAR_HEIGHT = 24.dp @@ -141,8 +149,9 @@ fun ProfileScreen( // Scroll state for collapsing header animation val density = LocalDensity.current - val expandedHeightPx = with(density) { EXPANDED_HEADER_HEIGHT.toPx() } - val collapsedHeightPx = with(density) { (COLLAPSED_HEADER_HEIGHT + STATUS_BAR_HEIGHT).toPx() } + val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val expandedHeightPx = with(density) { (EXPANDED_HEADER_HEIGHT + statusBarHeight).toPx() } + val collapsedHeightPx = with(density) { (COLLAPSED_HEADER_HEIGHT + statusBarHeight).toPx() } // Track scroll offset with animated state for smooth transitions var scrollOffset by remember { mutableFloatStateOf(0f) } @@ -426,36 +435,46 @@ private fun CollapsingProfileHeader( isDarkTheme: Boolean ) { val density = LocalDensity.current + val configuration = LocalConfiguration.current + val screenWidthDp = configuration.screenWidthDp.dp - // Animated values using lerp - val headerHeight = androidx.compose.ui.unit.lerp( - EXPANDED_HEADER_HEIGHT, - COLLAPSED_HEADER_HEIGHT + STATUS_BAR_HEIGHT, - collapseProgress - ) + // Get actual status bar height + val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - val avatarSize = androidx.compose.ui.unit.lerp( - AVATAR_SIZE_EXPANDED, - AVATAR_SIZE_COLLAPSED, - collapseProgress - ) + // Header heights + val expandedHeight = EXPANDED_HEADER_HEIGHT + statusBarHeight + val collapsedHeight = COLLAPSED_HEADER_HEIGHT + statusBarHeight - // Avatar position - smooth transition from center to left - val screenWidthDp = with(density) { 360.dp } // approximate - val avatarStartX = (screenWidthDp - AVATAR_SIZE_EXPANDED) / 2 - val avatarEndX = 56.dp // After back button + // Animated header height + val headerHeight = androidx.compose.ui.unit.lerp(expandedHeight, collapsedHeight, collapseProgress) - val avatarX = androidx.compose.ui.unit.lerp(avatarStartX, avatarEndX, collapseProgress) - val avatarY = androidx.compose.ui.unit.lerp(70.dp, STATUS_BAR_HEIGHT + 10.dp, collapseProgress) + // ═══════════════════════════════════════════════════════════ + // πŸ‘€ AVATAR - shrinks and moves UP until disappears + // ═══════════════════════════════════════════════════════════ + val avatarCenterX = (screenWidthDp - AVATAR_SIZE_EXPANDED) / 2 + val avatarStartY = statusBarHeight + 32.dp + val avatarEndY = statusBarHeight - 60.dp // Moves above screen + val avatarY = androidx.compose.ui.unit.lerp(avatarStartY, avatarEndY, collapseProgress) + val avatarSize = androidx.compose.ui.unit.lerp(AVATAR_SIZE_EXPANDED, 0.dp, collapseProgress) + val avatarFontSize = androidx.compose.ui.unit.lerp(40.sp, 0.sp, collapseProgress) - // Text alpha animations - val expandedContentAlpha = (1f - collapseProgress * 2f).coerceIn(0f, 1f) // Fades out faster - val collapsedContentAlpha = ((collapseProgress - 0.5f) * 2f).coerceIn(0f, 1f) // Fades in at 50% + // ═══════════════════════════════════════════════════════════ + // πŸ“ TEXT - moves from center to left corner + // ═══════════════════════════════════════════════════════════ + val textExpandedX = screenWidthDp / 2 + val textCollapsedX = 48.dp + 12.dp // After back button + gap (left aligned) + val textX = androidx.compose.ui.unit.lerp(textExpandedX, textCollapsedX, collapseProgress) - // Avatar scale for "drop" effect - shrinks faster at the end - val avatarScaleMultiplier = if (collapseProgress > 0.7f) { - 1f - ((collapseProgress - 0.7f) / 0.3f) * 0.15f - } else 1f + val textExpandedY = statusBarHeight + 32.dp + AVATAR_SIZE_EXPANDED + 48.dp + val textCollapsedY = statusBarHeight + COLLAPSED_HEADER_HEIGHT / 2 + val textY = androidx.compose.ui.unit.lerp(textExpandedY, textCollapsedY, collapseProgress) + + // Font sizes + val nameFontSize = androidx.compose.ui.unit.lerp(24.sp, 18.sp, collapseProgress) + val onlineFontSize = androidx.compose.ui.unit.lerp(14.sp, 13.sp, collapseProgress) + + // Text alignment interpolation (0 = center, 1 = left) + val textCenterOffset = androidx.compose.ui.unit.lerp(80.dp, 0.dp, collapseProgress) Box( modifier = Modifier @@ -466,123 +485,90 @@ private fun CollapsingProfileHeader( } ) { // ═══════════════════════════════════════════════════════════ - // πŸ‘€ ANIMATED AVATAR - Telegram "drop" effect + // πŸ”™ BACK BUTTON // ═══════════════════════════════════════════════════════════ Box( modifier = Modifier - .offset(x = avatarX, y = avatarY) - .size(avatarSize) - .graphicsLayer { - scaleX = avatarScaleMultiplier - scaleY = avatarScaleMultiplier - // Subtle alpha decrease at the very end for smooth collapse - alpha = if (collapseProgress > 0.9f) { - 1f - ((collapseProgress - 0.9f) / 0.1f) * 0.2f - } else 1f - } - .clip(CircleShape) - .background(Color.White.copy(alpha = 0.2f)) - .padding(2.dp) - .clip(CircleShape) - .background(avatarColors.backgroundColor), + .padding(top = statusBarHeight) + .padding(start = 4.dp, top = 4.dp) + .size(48.dp), contentAlignment = Alignment.Center ) { - Text( - text = getInitials(name), - fontSize = androidx.compose.ui.unit.lerp(40.sp, 14.sp, collapseProgress), - fontWeight = FontWeight.Bold, - color = avatarColors.textColor - ) + IconButton( + onClick = onBack, + modifier = Modifier.size(48.dp) + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.White, + modifier = Modifier.size(24.dp) + ) + } } // ═══════════════════════════════════════════════════════════ - // πŸ“ EXPANDED STATE - Name and info below avatar + // πŸ‘€ AVATAR - shrinks and moves up + // ═══════════════════════════════════════════════════════════ + if (avatarSize > 1.dp) { + Box( + modifier = Modifier + .offset( + x = avatarCenterX + (AVATAR_SIZE_EXPANDED - avatarSize) / 2, + y = avatarY + ) + .size(avatarSize) + .clip(CircleShape) + .background(Color.White.copy(alpha = 0.15f)) + .padding(2.dp) + .clip(CircleShape) + .background(avatarColors.backgroundColor), + contentAlignment = Alignment.Center + ) { + if (avatarFontSize > 1.sp) { + Text( + text = getInitials(name), + fontSize = avatarFontSize, + fontWeight = FontWeight.Bold, + color = avatarColors.textColor + ) + } + } + } + + // ═══════════════════════════════════════════════════════════ + // πŸ“ TEXT BLOCK - Name + Online, moves to corner, left-aligned when collapsed // ═══════════════════════════════════════════════════════════ Column( modifier = Modifier - .fillMaxWidth() - .padding(top = 200.dp) - .graphicsLayer { alpha = expandedContentAlpha }, - horizontalAlignment = Alignment.CenterHorizontally + .offset(x = textX - textCenterOffset, y = textY) + .graphicsLayer { + val centerOffsetY = with(density) { + androidx.compose.ui.unit.lerp(24.dp, 18.dp, collapseProgress).toPx() + } + translationY = -centerOffsetY + }, + horizontalAlignment = Alignment.Start ) { - // Name Text( text = name, - fontSize = 24.sp, + fontSize = nameFontSize, fontWeight = FontWeight.SemiBold, color = Color.White, - textAlign = TextAlign.Center + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.widthIn(max = 220.dp) ) - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.height(2.dp)) - // Username and public key - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - if (username.isNotBlank()) { - Text( - text = "@$username", - fontSize = 14.sp, - color = Color.White.copy(alpha = 0.8f) - ) - Text( - text = " β€’ ", - fontSize = 14.sp, - color = Color.White.copy(alpha = 0.8f) - ) - } - Text( - text = "${publicKey.take(3)}...${publicKey.takeLast(3)}", - fontSize = 14.sp, - color = Color.White.copy(alpha = 0.8f) - ) - } - } - - // ═══════════════════════════════════════════════════════════ - // πŸ“ COLLAPSED STATE - Toolbar with name next to avatar - // ═══════════════════════════════════════════════════════════ - Row( - modifier = Modifier - .fillMaxWidth() - .statusBarsPadding() - .height(COLLAPSED_HEADER_HEIGHT) - .padding(start = 100.dp) // Space for back button + avatar - .graphicsLayer { alpha = collapsedContentAlpha }, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text( - text = name, - fontSize = 18.sp, - fontWeight = FontWeight.SemiBold, - color = Color.White, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = "online", - fontSize = 13.sp, - color = Color.White.copy(alpha = 0.7f) - ) - } - } - - // ═══════════════════════════════════════════════════════════ - // πŸ”™ BACK BUTTON - Always visible - // ═══════════════════════════════════════════════════════════ - IconButton( - onClick = onBack, - modifier = Modifier - .statusBarsPadding() - .padding(4.dp) - ) { - Icon( - imageVector = Icons.Filled.ArrowBack, - contentDescription = "Back", - tint = Color.White + // Online text - centered in expanded, left in collapsed + val onlineCenterOffset = androidx.compose.ui.unit.lerp(65.dp, 0.dp, collapseProgress) + Text( + text = "online", + fontSize = onlineFontSize, + color = Color(0xFF4CAF50), + modifier = Modifier.offset(x = onlineCenterOffset) ) } @@ -595,7 +581,7 @@ private fun CollapsingProfileHeader( exit = fadeOut() + shrinkHorizontally(), modifier = Modifier .align(Alignment.TopEnd) - .statusBarsPadding() + .padding(top = statusBarHeight) .padding(4.dp) ) { TextButton(onClick = onSave) {