feat: Enhance ProfileScreen with improved collapsing header and back navigation

This commit is contained in:
k1ngsterr1
2026-01-21 02:17:17 +05:00
parent f856459494
commit 5145388e02

View File

@@ -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) {