feat: Implement collapsing header for ProfileScreen with enhanced navigation and logging features
This commit is contained in:
@@ -4,8 +4,10 @@ import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
@@ -16,22 +18,28 @@ import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
// 🎨 Avatar colors - используем те же цвета что и в ChatsListScreen
|
||||
private val avatarColorsLight =
|
||||
@@ -91,6 +99,15 @@ 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 AVATAR_SIZE_EXPANDED = 120.dp
|
||||
private val AVATAR_SIZE_COLLAPSED = 36.dp
|
||||
private val STATUS_BAR_HEIGHT = 24.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ProfileScreen(
|
||||
@@ -107,13 +124,12 @@ fun ProfileScreen(
|
||||
onNavigateToLogs: () -> Unit = {},
|
||||
viewModel: ProfileViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
|
||||
) {
|
||||
// Цвета в зависимости от темы - такие же как в ChatsListScreen
|
||||
// Цвета в зависимости от темы
|
||||
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
|
||||
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
|
||||
val iconTintColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
val avatarColors = getAvatarColor(accountPublicKey, isDarkTheme)
|
||||
|
||||
// State for editing
|
||||
var editedName by remember { mutableStateOf(accountName) }
|
||||
@@ -123,8 +139,50 @@ fun ProfileScreen(
|
||||
// ViewModel state
|
||||
val profileState by viewModel.state.collectAsState()
|
||||
|
||||
// Show success toast
|
||||
// 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() }
|
||||
|
||||
// Track scroll offset with animated state for smooth transitions
|
||||
var scrollOffset by remember { mutableFloatStateOf(0f) }
|
||||
val maxScrollOffset = expandedHeightPx - collapsedHeightPx
|
||||
|
||||
// Calculate collapse progress (0 = expanded, 1 = collapsed)
|
||||
val collapseProgress by remember {
|
||||
derivedStateOf {
|
||||
(scrollOffset / maxScrollOffset).coerceIn(0f, 1f)
|
||||
}
|
||||
}
|
||||
|
||||
// Nested scroll connection for tracking scroll
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
val delta = available.y
|
||||
val newOffset = scrollOffset - delta
|
||||
val consumed = when {
|
||||
delta < 0 && scrollOffset < maxScrollOffset -> {
|
||||
val consumed = (newOffset.coerceIn(0f, maxScrollOffset) - scrollOffset)
|
||||
scrollOffset = newOffset.coerceIn(0f, maxScrollOffset)
|
||||
-consumed
|
||||
}
|
||||
delta > 0 && scrollOffset > 0 -> {
|
||||
val consumed = scrollOffset - newOffset.coerceIn(0f, maxScrollOffset)
|
||||
scrollOffset = newOffset.coerceIn(0f, maxScrollOffset)
|
||||
consumed
|
||||
}
|
||||
else -> 0f
|
||||
}
|
||||
return Offset(0f, consumed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context
|
||||
val context = LocalContext.current
|
||||
|
||||
// Show success toast
|
||||
LaunchedEffect(profileState.saveSuccess) {
|
||||
if (profileState.saveSuccess) {
|
||||
android.widget.Toast.makeText(
|
||||
@@ -159,198 +217,365 @@ fun ProfileScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
) {
|
||||
Column(
|
||||
// Scrollable content
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(top = with(density) { (expandedHeightPx - scrollOffset).toDp() })
|
||||
) {
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 👤 PROFILE CARD with colored header
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
ProfileCard(
|
||||
name = editedName.ifBlank { accountPublicKey.take(10) },
|
||||
username = editedUsername,
|
||||
publicKey = accountPublicKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = null, // Кнопка назад будет снаружи
|
||||
hasChanges = hasChanges,
|
||||
onSave = {
|
||||
// Save via ViewModel
|
||||
viewModel.saveProfile(
|
||||
publicKey = accountPublicKey,
|
||||
privateKeyHash = accountPrivateKeyHash,
|
||||
name = editedName,
|
||||
username = editedUsername
|
||||
)
|
||||
// Also update local account name
|
||||
viewModel.updateLocalAccountName(accountPublicKey, editedName)
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// ✏️ EDITABLE FIELDS
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
ProfileSectionTitle(title = "Profile Information", isDarkTheme = isDarkTheme)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileEditableField(
|
||||
label = "Your name",
|
||||
value = editedName,
|
||||
onValueChange = { editedName = it },
|
||||
placeholder = "ex. Freddie Gibson",
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
ProfileEditableField(
|
||||
label = "Username",
|
||||
value = editedUsername,
|
||||
onValueChange = { editedUsername = it },
|
||||
placeholder = "ex. freddie871",
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// ✏️ EDITABLE FIELDS
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
ProfileSectionTitle(title = "Profile Information", isDarkTheme = isDarkTheme)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileEditableField(
|
||||
label = "Your name",
|
||||
value = editedName,
|
||||
onValueChange = { editedName = it },
|
||||
placeholder = "ex. Freddie Gibson",
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
ProfileEditableField(
|
||||
label = "Username",
|
||||
value = editedUsername,
|
||||
onValueChange = { editedUsername = it },
|
||||
placeholder = "ex. freddie871",
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Public Key Copy Field
|
||||
ProfileCopyField(
|
||||
label = "Public Key",
|
||||
value = accountPublicKey,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "This is your public key. If you haven't set a @username yet, you can ask a friend to message you using your public key.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🔧 SETTINGS SECTION
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
ProfileSectionTitle(title = "Settings", isDarkTheme = isDarkTheme)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Brush,
|
||||
iconBackground = Color(0xFF6366F1), // indigo
|
||||
title = "Theme",
|
||||
subtitle = "Customize appearance",
|
||||
onClick = onNavigateToTheme,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.AdminPanelSettings,
|
||||
iconBackground = Color(0xFF9333EA), // grape/purple
|
||||
title = "Safety",
|
||||
subtitle = "Backup and security settings",
|
||||
onClick = onNavigateToSafety,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "You can learn more about your safety on the safety page, please make sure you are viewing the screen alone before proceeding to the safety page.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// <20> DEBUG / LOGS SECTION
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.BugReport,
|
||||
iconBackground = Color(0xFFFB8C00), // orange
|
||||
title = "View Logs",
|
||||
subtitle = "Debug profile save operations",
|
||||
onClick = onNavigateToLogs,
|
||||
// Public Key Copy Field
|
||||
ProfileCopyField(
|
||||
label = "Public Key",
|
||||
value = accountPublicKey,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "View detailed logs of profile save operations for debugging.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// <20>🚪 LOGOUT SECTION
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Logout,
|
||||
iconBackground = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
|
||||
title = "Logout",
|
||||
subtitle = "Sign out of your account",
|
||||
onClick = onLogout,
|
||||
isDarkTheme = isDarkTheme,
|
||||
hideChevron = true,
|
||||
textColor = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)
|
||||
Text(
|
||||
text = "This is your public key. If you haven't set a @username yet, you can ask a friend to message you using your public key.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🔧 SETTINGS SECTION
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
ProfileSectionTitle(title = "Settings", isDarkTheme = isDarkTheme)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Brush,
|
||||
iconBackground = Color(0xFF6366F1),
|
||||
title = "Theme",
|
||||
subtitle = "Customize appearance",
|
||||
onClick = onNavigateToTheme,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.AdminPanelSettings,
|
||||
iconBackground = Color(0xFF9333EA),
|
||||
title = "Safety",
|
||||
subtitle = "Backup and security settings",
|
||||
onClick = onNavigateToSafety,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "You can learn more about your safety on the safety page, please make sure you are viewing the screen alone before proceeding to the safety page.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🐛 DEBUG / LOGS SECTION
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.BugReport,
|
||||
iconBackground = Color(0xFFFB8C00),
|
||||
title = "View Logs",
|
||||
subtitle = "Debug profile save operations",
|
||||
onClick = onNavigateToLogs,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "View detailed logs of profile save operations for debugging.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🚪 LOGOUT SECTION
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Logout,
|
||||
iconBackground = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
|
||||
title = "Logout",
|
||||
subtitle = "Sign out of your account",
|
||||
onClick = onLogout,
|
||||
isDarkTheme = isDarkTheme,
|
||||
hideChevron = true,
|
||||
textColor = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Logging out of your account. After logging out, you will be redirected to the password entry page.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Logging out of your account. After logging out, you will be redirected to the password entry page.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
|
||||
// Fixed back button at top
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🎨 COLLAPSING HEADER - Telegram style
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
CollapsingProfileHeader(
|
||||
name = editedName.ifBlank { accountPublicKey.take(10) },
|
||||
username = editedUsername,
|
||||
publicKey = accountPublicKey,
|
||||
avatarColors = avatarColors,
|
||||
collapseProgress = collapseProgress,
|
||||
onBack = onBack,
|
||||
hasChanges = hasChanges,
|
||||
onSave = {
|
||||
viewModel.saveProfile(
|
||||
publicKey = accountPublicKey,
|
||||
privateKeyHash = accountPrivateKeyHash,
|
||||
name = editedName,
|
||||
username = editedUsername
|
||||
)
|
||||
viewModel.updateLocalAccountName(accountPublicKey, editedName)
|
||||
},
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🎯 COLLAPSING PROFILE HEADER - Telegram Style Animation
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
@Composable
|
||||
private fun CollapsingProfileHeader(
|
||||
name: String,
|
||||
username: String,
|
||||
publicKey: String,
|
||||
avatarColors: AvatarColors,
|
||||
collapseProgress: Float,
|
||||
onBack: () -> Unit,
|
||||
hasChanges: Boolean,
|
||||
onSave: () -> Unit,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
|
||||
// Animated values using lerp
|
||||
val headerHeight = androidx.compose.ui.unit.lerp(
|
||||
EXPANDED_HEADER_HEIGHT,
|
||||
COLLAPSED_HEADER_HEIGHT + STATUS_BAR_HEIGHT,
|
||||
collapseProgress
|
||||
)
|
||||
|
||||
val avatarSize = androidx.compose.ui.unit.lerp(
|
||||
AVATAR_SIZE_EXPANDED,
|
||||
AVATAR_SIZE_COLLAPSED,
|
||||
collapseProgress
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
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)
|
||||
|
||||
// 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%
|
||||
|
||||
// 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
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(headerHeight)
|
||||
.drawBehind {
|
||||
drawRect(avatarColors.backgroundColor)
|
||||
}
|
||||
) {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 👤 ANIMATED AVATAR - Telegram "drop" effect
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
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),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = getInitials(name),
|
||||
fontSize = androidx.compose.ui.unit.lerp(40.sp, 14.sp, collapseProgress),
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = avatarColors.textColor
|
||||
)
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 📝 EXPANDED STATE - Name and info below avatar
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 200.dp)
|
||||
.graphicsLayer { alpha = expandedContentAlpha },
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Name
|
||||
Text(
|
||||
text = name,
|
||||
fontSize = 24.sp,
|
||||
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,
|
||||
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
|
||||
.align(Alignment.TopStart)
|
||||
.statusBarsPadding()
|
||||
.padding(4.dp)
|
||||
) {
|
||||
@@ -361,7 +586,9 @@ fun ProfileScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// Fixed save button (if changes)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 💾 SAVE BUTTON (if changes)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
AnimatedVisibility(
|
||||
visible = hasChanges,
|
||||
enter = fadeIn() + expandHorizontally(),
|
||||
@@ -371,10 +598,7 @@ fun ProfileScreen(
|
||||
.statusBarsPadding()
|
||||
.padding(4.dp)
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
onSaveProfile(editedName, editedUsername)
|
||||
hasChanges = false
|
||||
}) {
|
||||
TextButton(onClick = onSave) {
|
||||
Text(
|
||||
text = "Save",
|
||||
color = Color.White,
|
||||
@@ -386,7 +610,7 @@ fun ProfileScreen(
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 📦 PROFILE CARD COMPONENT - Large Avatar Telegram Style
|
||||
// 📦 PROFILE CARD COMPONENT - Legacy (kept for OtherProfileScreen)
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
@Composable
|
||||
fun ProfileCard(
|
||||
@@ -649,7 +873,7 @@ private fun ProfileCopyField(
|
||||
Text(
|
||||
text = "Copied!",
|
||||
fontSize = 14.sp,
|
||||
color = Color(0xFF22C55E), // green
|
||||
color = Color(0xFF22C55E),
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user