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.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.util.Log
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* 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.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape 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.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll 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.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -41,6 +47,8 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt
private const val TAG = "ProfileScreen"
// 🎨 Avatar colors - используем те же цвета что и в ChatsListScreen // 🎨 Avatar colors - используем те же цвета что и в ChatsListScreen
private val avatarColorsLight = private val avatarColorsLight =
listOf( listOf(
@@ -103,7 +111,7 @@ private fun getInitials(name: String): String {
// 🎯 COLLAPSING HEADER CONSTANTS // 🎯 COLLAPSING HEADER CONSTANTS
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
private val EXPANDED_HEADER_HEIGHT = 280.dp 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_EXPANDED = 120.dp
private val AVATAR_SIZE_COLLAPSED = 36.dp private val AVATAR_SIZE_COLLAPSED = 36.dp
private val STATUS_BAR_HEIGHT = 24.dp private val STATUS_BAR_HEIGHT = 24.dp
@@ -141,8 +149,9 @@ fun ProfileScreen(
// Scroll state for collapsing header animation // Scroll state for collapsing header animation
val density = LocalDensity.current val density = LocalDensity.current
val expandedHeightPx = with(density) { EXPANDED_HEADER_HEIGHT.toPx() } val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
val collapsedHeightPx = with(density) { (COLLAPSED_HEADER_HEIGHT + STATUS_BAR_HEIGHT).toPx() } 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 // Track scroll offset with animated state for smooth transitions
var scrollOffset by remember { mutableFloatStateOf(0f) } var scrollOffset by remember { mutableFloatStateOf(0f) }
@@ -426,36 +435,46 @@ private fun CollapsingProfileHeader(
isDarkTheme: Boolean isDarkTheme: Boolean
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
val configuration = LocalConfiguration.current
val screenWidthDp = configuration.screenWidthDp.dp
// Animated values using lerp // Get actual status bar height
val headerHeight = androidx.compose.ui.unit.lerp( val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
EXPANDED_HEADER_HEIGHT,
COLLAPSED_HEADER_HEIGHT + STATUS_BAR_HEIGHT,
collapseProgress
)
val avatarSize = androidx.compose.ui.unit.lerp( // Header heights
AVATAR_SIZE_EXPANDED, val expandedHeight = EXPANDED_HEADER_HEIGHT + statusBarHeight
AVATAR_SIZE_COLLAPSED, val collapsedHeight = COLLAPSED_HEADER_HEIGHT + statusBarHeight
collapseProgress
)
// Avatar position - smooth transition from center to left // Animated header height
val screenWidthDp = with(density) { 360.dp } // approximate val headerHeight = androidx.compose.ui.unit.lerp(expandedHeight, collapsedHeight, collapseProgress)
val avatarStartX = (screenWidthDp - AVATAR_SIZE_EXPANDED) / 2
val avatarEndX = 56.dp // After back button
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 // 📝 TEXT - moves from center to left corner
val collapsedContentAlpha = ((collapseProgress - 0.5f) * 2f).coerceIn(0f, 1f) // Fades in at 50% // ═══════════════════════════════════════════════════════════
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 textExpandedY = statusBarHeight + 32.dp + AVATAR_SIZE_EXPANDED + 48.dp
val avatarScaleMultiplier = if (collapseProgress > 0.7f) { val textCollapsedY = statusBarHeight + COLLAPSED_HEADER_HEIGHT / 2
1f - ((collapseProgress - 0.7f) / 0.3f) * 0.15f val textY = androidx.compose.ui.unit.lerp(textExpandedY, textCollapsedY, collapseProgress)
} else 1f
// 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( Box(
modifier = Modifier modifier = Modifier
@@ -466,123 +485,90 @@ private fun CollapsingProfileHeader(
} }
) { ) {
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// 👤 ANIMATED AVATAR - Telegram "drop" effect // 🔙 BACK BUTTON
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
Box( Box(
modifier = Modifier modifier = Modifier
.offset(x = avatarX, y = avatarY) .padding(top = statusBarHeight)
.size(avatarSize) .padding(start = 4.dp, top = 4.dp)
.graphicsLayer { .size(48.dp),
scaleX = avatarScaleMultiplier contentAlignment = Alignment.Center
scaleY = avatarScaleMultiplier ) {
// Subtle alpha decrease at the very end for smooth collapse IconButton(
alpha = if (collapseProgress > 0.9f) { onClick = onBack,
1f - ((collapseProgress - 0.9f) / 0.1f) * 0.2f modifier = Modifier.size(48.dp)
} else 1f ) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
tint = Color.White,
modifier = Modifier.size(24.dp)
)
} }
}
// ═══════════════════════════════════════════════════════════
// 👤 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) .clip(CircleShape)
.background(Color.White.copy(alpha = 0.2f)) .background(Color.White.copy(alpha = 0.15f))
.padding(2.dp) .padding(2.dp)
.clip(CircleShape) .clip(CircleShape)
.background(avatarColors.backgroundColor), .background(avatarColors.backgroundColor),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
if (avatarFontSize > 1.sp) {
Text( Text(
text = getInitials(name), text = getInitials(name),
fontSize = androidx.compose.ui.unit.lerp(40.sp, 14.sp, collapseProgress), fontSize = avatarFontSize,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = avatarColors.textColor color = avatarColors.textColor
) )
} }
}
}
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// 📝 EXPANDED STATE - Name and info below avatar // 📝 TEXT BLOCK - Name + Online, moves to corner, left-aligned when collapsed
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .offset(x = textX - textCenterOffset, y = textY)
.padding(top = 200.dp) .graphicsLayer {
.graphicsLayer { alpha = expandedContentAlpha }, val centerOffsetY = with(density) {
horizontalAlignment = Alignment.CenterHorizontally androidx.compose.ui.unit.lerp(24.dp, 18.dp, collapseProgress).toPx()
}
translationY = -centerOffsetY
},
horizontalAlignment = Alignment.Start
) { ) {
// Name
Text( Text(
text = name, text = name,
fontSize = 24.sp, fontSize = nameFontSize,
fontWeight = FontWeight.SemiBold,
color = Color.White,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(4.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, fontWeight = FontWeight.SemiBold,
color = Color.White, color = Color.White,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis,
modifier = Modifier.widthIn(max = 220.dp)
) )
Spacer(modifier = Modifier.height(2.dp))
// Online text - centered in expanded, left in collapsed
val onlineCenterOffset = androidx.compose.ui.unit.lerp(65.dp, 0.dp, collapseProgress)
Text( Text(
text = "online", text = "online",
fontSize = 13.sp, fontSize = onlineFontSize,
color = Color.White.copy(alpha = 0.7f) color = Color(0xFF4CAF50),
) modifier = Modifier.offset(x = onlineCenterOffset)
}
}
// ═══════════════════════════════════════════════════════════
// 🔙 BACK BUTTON - Always visible
// ═══════════════════════════════════════════════════════════
IconButton(
onClick = onBack,
modifier = Modifier
.statusBarsPadding()
.padding(4.dp)
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
tint = Color.White
) )
} }
@@ -595,7 +581,7 @@ private fun CollapsingProfileHeader(
exit = fadeOut() + shrinkHorizontally(), exit = fadeOut() + shrinkHorizontally(),
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.statusBarsPadding() .padding(top = statusBarHeight)
.padding(4.dp) .padding(4.dp)
) { ) {
TextButton(onClick = onSave) { TextButton(onClick = onSave) {