refactor: Clean up imports and improve code formatting in OtherProfileScreen
This commit is contained in:
@@ -1,32 +1,21 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.outlined.Block
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.rosetta.messenger.data.MessageRepository
|
||||
import com.rosetta.messenger.database.RosettaDatabase
|
||||
import com.rosetta.messenger.ui.chats.ChatViewModel
|
||||
import com.rosetta.messenger.ui.chats.ChatsListViewModel
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
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
|
||||
@@ -41,17 +30,22 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
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.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.rosetta.messenger.data.MessageRepository
|
||||
import com.rosetta.messenger.database.RosettaDatabase
|
||||
import com.rosetta.messenger.network.SearchUser
|
||||
import com.rosetta.messenger.repository.AvatarRepository
|
||||
import com.rosetta.messenger.ui.chats.ChatViewModel
|
||||
import com.rosetta.messenger.ui.chats.ChatsListViewModel
|
||||
import com.rosetta.messenger.ui.components.AvatarImage
|
||||
import com.rosetta.messenger.ui.components.BlurredAvatarBackground
|
||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
// Collapsing header constants
|
||||
private val EXPANDED_HEADER_HEIGHT_OTHER = 280.dp
|
||||
@@ -62,10 +56,10 @@ private val AVATAR_SIZE_COLLAPSED_OTHER = 36.dp
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun OtherProfileScreen(
|
||||
user: SearchUser,
|
||||
isDarkTheme: Boolean,
|
||||
onBack: () -> Unit,
|
||||
avatarRepository: AvatarRepository? = null
|
||||
user: SearchUser,
|
||||
isDarkTheme: Boolean,
|
||||
onBack: () -> Unit,
|
||||
avatarRepository: AvatarRepository? = null
|
||||
) {
|
||||
var isBlocked by remember { mutableStateOf(false) }
|
||||
var showAvatarMenu by remember { mutableStateOf(false) }
|
||||
@@ -75,91 +69,98 @@ fun OtherProfileScreen(
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
val avatarColors = getAvatarColor(user.publicKey, isDarkTheme)
|
||||
val context = LocalContext.current
|
||||
|
||||
|
||||
// 🔥 Получаем тот же ChatViewModel что и в ChatDetailScreen для очистки истории
|
||||
val viewModel: ChatViewModel = viewModel(key = "chat_${user.publicKey}")
|
||||
|
||||
|
||||
// 🔥 ChatsListViewModel для блокировки/разблокировки
|
||||
val chatsListViewModel: ChatsListViewModel = viewModel()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
|
||||
// 🔥 Загружаем статус блокировки при открытии экрана
|
||||
LaunchedEffect(user.publicKey) {
|
||||
isBlocked = chatsListViewModel.isUserBlocked(user.publicKey)
|
||||
}
|
||||
|
||||
LaunchedEffect(user.publicKey) { isBlocked = chatsListViewModel.isUserBlocked(user.publicKey) }
|
||||
|
||||
// 🎹 Для закрытия клавиатуры
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
|
||||
// 🗑️ Для удаления диалога
|
||||
val database = remember { RosettaDatabase.getDatabase(context) }
|
||||
|
||||
|
||||
// 🔥 Закрываем клавиатуру при открытии экрана
|
||||
LaunchedEffect(Unit) {
|
||||
keyboardController?.hide()
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
|
||||
|
||||
// <20>🟢 Наблюдаем за онлайн статусом пользователя в реальном времени
|
||||
val messageRepository = remember { MessageRepository.getInstance(context) }
|
||||
val onlineStatus by messageRepository.observeUserOnlineStatus(user.publicKey)
|
||||
.collectAsState(initial = false to 0L)
|
||||
val onlineStatus by
|
||||
messageRepository
|
||||
.observeUserOnlineStatus(user.publicKey)
|
||||
.collectAsState(initial = false to 0L)
|
||||
val isOnline = onlineStatus.first
|
||||
val lastSeen = onlineStatus.second
|
||||
|
||||
|
||||
// Scroll state for collapsing header animation
|
||||
val density = LocalDensity.current
|
||||
val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
val expandedHeightPx = with(density) { (EXPANDED_HEADER_HEIGHT_OTHER + statusBarHeight).toPx() }
|
||||
val collapsedHeightPx = with(density) { (COLLAPSED_HEADER_HEIGHT_OTHER + statusBarHeight).toPx() }
|
||||
|
||||
val collapsedHeightPx =
|
||||
with(density) { (COLLAPSED_HEADER_HEIGHT_OTHER + statusBarHeight).toPx() }
|
||||
|
||||
var scrollOffset by remember { mutableFloatStateOf(0f) }
|
||||
val maxScrollOffset = expandedHeightPx - collapsedHeightPx
|
||||
|
||||
|
||||
val collapseProgress by remember {
|
||||
derivedStateOf {
|
||||
(scrollOffset / maxScrollOffset).coerceIn(0f, 1f)
|
||||
}
|
||||
derivedStateOf { (scrollOffset / maxScrollOffset).coerceIn(0f, 1f) }
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle back gesture
|
||||
BackHandler { onBack() }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
) {
|
||||
// Scrollable content
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = with(density) { (expandedHeightPx - scrollOffset).toDp() })
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.padding(
|
||||
top =
|
||||
with(density) {
|
||||
(expandedHeightPx - scrollOffset).toDp()
|
||||
}
|
||||
)
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
@@ -169,25 +170,25 @@ fun OtherProfileScreen(
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
if (user.username.isNotBlank()) {
|
||||
TelegramSectionTitle(title = "Info", isDarkTheme = isDarkTheme)
|
||||
|
||||
|
||||
TelegramCopyField(
|
||||
value = "@${user.username}",
|
||||
fullValue = user.username,
|
||||
label = "Username",
|
||||
isDarkTheme = isDarkTheme
|
||||
value = "@${user.username}",
|
||||
fullValue = user.username,
|
||||
label = "Username",
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
|
||||
// Public Key Section
|
||||
TelegramSectionTitle(title = "Public Key", isDarkTheme = isDarkTheme)
|
||||
|
||||
|
||||
TelegramCopyField(
|
||||
value = user.publicKey.take(16) + "..." + user.publicKey.takeLast(6),
|
||||
fullValue = user.publicKey,
|
||||
label = "Public Key",
|
||||
isDarkTheme = isDarkTheme
|
||||
value = user.publicKey.take(16) + "..." + user.publicKey.takeLast(6),
|
||||
fullValue = user.publicKey,
|
||||
label = "Public Key",
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
@@ -198,43 +199,43 @@ fun OtherProfileScreen(
|
||||
// 🎨 COLLAPSING HEADER
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
CollapsingOtherProfileHeader(
|
||||
name = user.title.ifEmpty { "Unknown User" },
|
||||
username = user.username,
|
||||
publicKey = user.publicKey,
|
||||
verified = user.verified,
|
||||
isOnline = isOnline,
|
||||
lastSeen = lastSeen,
|
||||
avatarColors = avatarColors,
|
||||
collapseProgress = collapseProgress,
|
||||
onBack = onBack,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showAvatarMenu = showAvatarMenu,
|
||||
onAvatarMenuChange = { showAvatarMenu = it },
|
||||
isBlocked = isBlocked,
|
||||
onBlockToggle = {
|
||||
coroutineScope.launch {
|
||||
if (isBlocked) {
|
||||
chatsListViewModel.unblockUser(user.publicKey)
|
||||
} else {
|
||||
chatsListViewModel.blockUser(user.publicKey)
|
||||
name = user.title.ifEmpty { "Unknown User" },
|
||||
username = user.username,
|
||||
publicKey = user.publicKey,
|
||||
verified = user.verified,
|
||||
isOnline = isOnline,
|
||||
lastSeen = lastSeen,
|
||||
avatarColors = avatarColors,
|
||||
collapseProgress = collapseProgress,
|
||||
onBack = onBack,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showAvatarMenu = showAvatarMenu,
|
||||
onAvatarMenuChange = { showAvatarMenu = it },
|
||||
isBlocked = isBlocked,
|
||||
onBlockToggle = {
|
||||
coroutineScope.launch {
|
||||
if (isBlocked) {
|
||||
chatsListViewModel.unblockUser(user.publicKey)
|
||||
} else {
|
||||
chatsListViewModel.blockUser(user.publicKey)
|
||||
}
|
||||
// Обновляем локальное состояние
|
||||
isBlocked = !isBlocked
|
||||
}
|
||||
// Обновляем локальное состояние
|
||||
isBlocked = !isBlocked
|
||||
}
|
||||
},
|
||||
avatarRepository = avatarRepository,
|
||||
onClearChat = {
|
||||
viewModel.clearChatHistory()
|
||||
// 🗑️ Удаляем диалог из списка после очистки истории
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val account = viewModel.myPublicKey ?: return@launch
|
||||
database.dialogDao().deleteDialog(account, user.publicKey)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("OtherProfileScreen", "Failed to delete dialog", e)
|
||||
},
|
||||
avatarRepository = avatarRepository,
|
||||
onClearChat = {
|
||||
viewModel.clearChatHistory()
|
||||
// 🗑️ Удаляем диалог из списка после очистки истории
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val account = viewModel.myPublicKey ?: return@launch
|
||||
database.dialogDao().deleteDialog(account, user.publicKey)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("OtherProfileScreen", "Failed to delete dialog", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -243,205 +244,207 @@ fun OtherProfileScreen(
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@Composable
|
||||
private fun CollapsingOtherProfileHeader(
|
||||
name: String,
|
||||
username: String,
|
||||
publicKey: String,
|
||||
verified: Int,
|
||||
isOnline: Boolean,
|
||||
lastSeen: Long,
|
||||
avatarColors: AvatarColors,
|
||||
collapseProgress: Float,
|
||||
onBack: () -> Unit,
|
||||
isDarkTheme: Boolean,
|
||||
showAvatarMenu: Boolean,
|
||||
onAvatarMenuChange: (Boolean) -> Unit,
|
||||
isBlocked: Boolean,
|
||||
onBlockToggle: () -> Unit,
|
||||
avatarRepository: AvatarRepository? = null,
|
||||
onClearChat: () -> Unit
|
||||
name: String,
|
||||
username: String,
|
||||
publicKey: String,
|
||||
verified: Int,
|
||||
isOnline: Boolean,
|
||||
lastSeen: Long,
|
||||
avatarColors: AvatarColors,
|
||||
collapseProgress: Float,
|
||||
onBack: () -> Unit,
|
||||
isDarkTheme: Boolean,
|
||||
showAvatarMenu: Boolean,
|
||||
onAvatarMenuChange: (Boolean) -> Unit,
|
||||
isBlocked: Boolean,
|
||||
onBlockToggle: () -> Unit,
|
||||
avatarRepository: AvatarRepository? = null,
|
||||
onClearChat: () -> Unit
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidthDp = configuration.screenWidthDp.dp
|
||||
|
||||
|
||||
val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
|
||||
|
||||
val expandedHeight = EXPANDED_HEADER_HEIGHT_OTHER + statusBarHeight
|
||||
val collapsedHeight = COLLAPSED_HEADER_HEIGHT_OTHER + statusBarHeight
|
||||
|
||||
val headerHeight = androidx.compose.ui.unit.lerp(expandedHeight, collapsedHeight, collapseProgress)
|
||||
|
||||
|
||||
val headerHeight =
|
||||
androidx.compose.ui.unit.lerp(expandedHeight, collapsedHeight, collapseProgress)
|
||||
|
||||
// Avatar animation
|
||||
val avatarCenterX = (screenWidthDp - AVATAR_SIZE_EXPANDED_OTHER) / 2
|
||||
val avatarStartY = statusBarHeight + 32.dp
|
||||
val avatarEndY = statusBarHeight - 60.dp
|
||||
val avatarY = androidx.compose.ui.unit.lerp(avatarStartY, avatarEndY, collapseProgress)
|
||||
val avatarSize = androidx.compose.ui.unit.lerp(AVATAR_SIZE_EXPANDED_OTHER, 0.dp, collapseProgress)
|
||||
val avatarSize =
|
||||
androidx.compose.ui.unit.lerp(AVATAR_SIZE_EXPANDED_OTHER, 0.dp, collapseProgress)
|
||||
val avatarFontSize = androidx.compose.ui.unit.lerp(40.sp, 0.sp, collapseProgress)
|
||||
|
||||
|
||||
// Text animation - always centered
|
||||
val textExpandedY = statusBarHeight + 32.dp + AVATAR_SIZE_EXPANDED_OTHER + 48.dp
|
||||
val textCollapsedY = statusBarHeight + COLLAPSED_HEADER_HEIGHT_OTHER / 2
|
||||
val textY = androidx.compose.ui.unit.lerp(textExpandedY, textCollapsedY, collapseProgress)
|
||||
|
||||
|
||||
val nameFontSize = androidx.compose.ui.unit.lerp(24.sp, 18.sp, collapseProgress)
|
||||
val onlineFontSize = androidx.compose.ui.unit.lerp(14.sp, 13.sp, collapseProgress)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(headerHeight)
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxWidth().height(headerHeight)) {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 🎨 BLURRED AVATAR BACKGROUND
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
BlurredAvatarBackground(
|
||||
publicKey = publicKey,
|
||||
avatarRepository = avatarRepository,
|
||||
fallbackColor = avatarColors.backgroundColor,
|
||||
blurRadius = 25f,
|
||||
alpha = 0.3f
|
||||
publicKey = publicKey,
|
||||
avatarRepository = avatarRepository,
|
||||
fallbackColor = avatarColors.backgroundColor,
|
||||
blurRadius = 25f,
|
||||
alpha = 0.3f
|
||||
)
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 🔙 BACK BUTTON
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = statusBarHeight)
|
||||
.padding(start = 4.dp, top = 4.dp)
|
||||
.size(48.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
modifier =
|
||||
Modifier.padding(top = statusBarHeight)
|
||||
.padding(start = 4.dp, top = 4.dp)
|
||||
.size(48.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
|
||||
modifier = Modifier.size(24.dp)
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = if (isDarkTheme) Color.White else Color(0xFF007AFF),
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// ⋮ MENU BUTTON (top right corner)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = statusBarHeight)
|
||||
.padding(end = 4.dp, top = 4.dp)
|
||||
.size(48.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
modifier =
|
||||
Modifier.align(Alignment.TopEnd)
|
||||
.padding(top = statusBarHeight)
|
||||
.padding(end = 4.dp, top = 4.dp)
|
||||
.size(48.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { onAvatarMenuChange(true) },
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
IconButton(onClick = { onAvatarMenuChange(true) }, modifier = Modifier.size(48.dp)) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "Profile menu",
|
||||
tint = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
|
||||
modifier = Modifier.size(24.dp)
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "Profile menu",
|
||||
tint =
|
||||
if (isColorLight(avatarColors.backgroundColor)) Color.Black
|
||||
else Color.White,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Меню с блокировкой пользователя
|
||||
com.rosetta.messenger.ui.chats.components.OtherProfileMenu(
|
||||
expanded = showAvatarMenu,
|
||||
onDismiss = { onAvatarMenuChange(false) },
|
||||
isDarkTheme = isDarkTheme,
|
||||
isBlocked = isBlocked,
|
||||
onBlockClick = {
|
||||
onAvatarMenuChange(false)
|
||||
onBlockToggle()
|
||||
},
|
||||
onClearChatClick = {
|
||||
onAvatarMenuChange(false)
|
||||
onClearChat()
|
||||
}
|
||||
expanded = showAvatarMenu,
|
||||
onDismiss = { onAvatarMenuChange(false) },
|
||||
isDarkTheme = isDarkTheme,
|
||||
isBlocked = isBlocked,
|
||||
onBlockClick = {
|
||||
onAvatarMenuChange(false)
|
||||
onBlockToggle()
|
||||
},
|
||||
onClearChatClick = {
|
||||
onAvatarMenuChange(false)
|
||||
onClearChat()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 👤 AVATAR - shrinks and moves up
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
if (avatarSize > 1.dp) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.offset(
|
||||
x = avatarCenterX + (AVATAR_SIZE_EXPANDED_OTHER - avatarSize) / 2,
|
||||
y = avatarY
|
||||
)
|
||||
.size(avatarSize)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White.copy(alpha = 0.15f))
|
||||
.padding(2.dp)
|
||||
.clip(CircleShape),
|
||||
contentAlignment = Alignment.Center
|
||||
modifier =
|
||||
Modifier.offset(
|
||||
x =
|
||||
avatarCenterX +
|
||||
(AVATAR_SIZE_EXPANDED_OTHER -
|
||||
avatarSize) / 2,
|
||||
y = avatarY
|
||||
)
|
||||
.size(avatarSize)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White.copy(alpha = 0.15f))
|
||||
.padding(2.dp)
|
||||
.clip(CircleShape),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AvatarImage(
|
||||
publicKey = publicKey,
|
||||
avatarRepository = avatarRepository,
|
||||
size = avatarSize - 4.dp,
|
||||
isDarkTheme = isDarkTheme
|
||||
publicKey = publicKey,
|
||||
avatarRepository = avatarRepository,
|
||||
size = avatarSize - 4.dp,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 📝 TEXT BLOCK - Name + Verified + Online, always centered
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.offset(y = textY)
|
||||
.graphicsLayer {
|
||||
val centerOffsetY = with(density) {
|
||||
androidx.compose.ui.unit.lerp(24.dp, 18.dp, collapseProgress).toPx()
|
||||
}
|
||||
translationY = -centerOffsetY
|
||||
},
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
modifier =
|
||||
Modifier.align(Alignment.TopCenter).offset(y = textY).graphicsLayer {
|
||||
val centerOffsetY =
|
||||
with(density) {
|
||||
androidx.compose
|
||||
.ui
|
||||
.unit
|
||||
.lerp(24.dp, 18.dp, collapseProgress)
|
||||
.toPx()
|
||||
}
|
||||
translationY = -centerOffsetY
|
||||
},
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Name + Verified Badge
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = name,
|
||||
fontSize = nameFontSize,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center
|
||||
text = name,
|
||||
fontSize = nameFontSize,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color =
|
||||
if (isColorLight(avatarColors.backgroundColor)) Color.Black
|
||||
else Color.White,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
|
||||
if (verified > 0) {
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
VerifiedBadge(
|
||||
verified = verified,
|
||||
size = (nameFontSize.value * 0.8f).toInt()
|
||||
)
|
||||
VerifiedBadge(verified = verified, size = (nameFontSize.value * 0.8f).toInt())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
|
||||
|
||||
// Online/Offline status
|
||||
Text(
|
||||
text = if (isOnline) "online" else "offline",
|
||||
fontSize = onlineFontSize,
|
||||
color = if (isOnline) {
|
||||
Color(0xFF4CAF50)
|
||||
} else {
|
||||
if (isColorLight(avatarColors.backgroundColor)) Color.Black.copy(alpha = 0.6f) else Color.White.copy(alpha = 0.6f)
|
||||
}
|
||||
text = if (isOnline) "online" else "offline",
|
||||
fontSize = onlineFontSize,
|
||||
color =
|
||||
if (isOnline) {
|
||||
Color(0xFF4CAF50)
|
||||
} else {
|
||||
if (isColorLight(avatarColors.backgroundColor))
|
||||
Color.Black.copy(alpha = 0.6f)
|
||||
else Color.White.copy(alpha = 0.6f)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -451,44 +454,42 @@ private fun CollapsingOtherProfileHeader(
|
||||
// 🚫 BLOCK/UNBLOCK ITEM
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@Composable
|
||||
private fun TelegramBlockItem(
|
||||
isBlocked: Boolean,
|
||||
onToggle: () -> Unit,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
private fun TelegramBlockItem(isBlocked: Boolean, onToggle: () -> Unit, isDarkTheme: Boolean) {
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val iconColor = PrimaryBlue
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onToggle)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clickable(onClick = onToggle)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Block,
|
||||
contentDescription = null,
|
||||
tint = iconColor,
|
||||
modifier = Modifier.size(24.dp)
|
||||
imageVector = Icons.Outlined.Block,
|
||||
contentDescription = null,
|
||||
tint = iconColor,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = if (isBlocked) "Unblock User" else "Block User",
|
||||
fontSize = 16.sp,
|
||||
color = iconColor,
|
||||
fontWeight = FontWeight.Medium
|
||||
text = if (isBlocked) "Unblock User" else "Block User",
|
||||
fontSize = 16.sp,
|
||||
color = iconColor,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = if (isBlocked) "Allow this user to message you" else "Prevent this user from messaging you",
|
||||
fontSize = 13.sp,
|
||||
color = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666),
|
||||
lineHeight = 16.sp
|
||||
text =
|
||||
if (isBlocked) "Allow this user to message you"
|
||||
else "Prevent this user from messaging you",
|
||||
fontSize = 13.sp,
|
||||
color = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user