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