feat: implement swipe back navigation and integrate VerifiedBadge in chat dialogs

This commit is contained in:
2026-02-05 03:25:20 +05:00
parent a03e267050
commit 9010d1c975
6 changed files with 555 additions and 295 deletions

View File

@@ -2115,6 +2115,7 @@ fun ChatDetailScreen(
dialogs = dialogsList,
isDarkTheme = isDarkTheme,
currentUserPublicKey = currentUserPublicKey,
avatarRepository = avatarRepository,
onDismiss = {
showForwardPicker = false
ForwardManager.clear()

View File

@@ -46,6 +46,7 @@ import com.rosetta.messenger.network.ProtocolState
import com.rosetta.messenger.ui.components.AppleEmojiText
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 java.text.SimpleDateFormat
import java.util.*
@@ -1807,15 +1808,23 @@ fun DialogItemContent(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = displayName,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp,
color = textColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
Row(
modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = displayName,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp,
color = textColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (dialog.verified > 0) {
Spacer(modifier = Modifier.width(4.dp))
VerifiedBadge(verified = dialog.verified, size = 16)
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -1932,6 +1941,7 @@ fun DialogItemContent(
dialog.lastMessageAttachmentType == "Photo" -> "Photo"
dialog.lastMessageAttachmentType == "File" -> "File"
dialog.lastMessageAttachmentType == "Avatar" -> "Avatar"
dialog.lastMessageAttachmentType == "Forwarded" -> "Forwarded message"
dialog.lastMessage.isEmpty() -> "No messages"
else -> dialog.lastMessage
}

View File

@@ -174,6 +174,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
val type = firstAttachment.optInt("type", -1)
when (type) {
0 -> "Photo" // AttachmentType.IMAGE = 0
1 -> "Forwarded" // AttachmentType.MESSAGES = 1
2 -> "File" // AttachmentType.FILE = 2
3 -> "Avatar" // AttachmentType.AVATAR = 3
else -> null
@@ -261,6 +262,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
val type = firstAttachment.optInt("type", -1)
when (type) {
0 -> "Photo" // AttachmentType.IMAGE = 0
1 -> "Forwarded" // AttachmentType.MESSAGES = 1
2 -> "File" // AttachmentType.FILE = 2
3 -> "Avatar" // AttachmentType.AVATAR = 3
else -> null

View File

@@ -1,5 +1,6 @@
package com.rosetta.messenger.ui.chats
import android.view.HapticFeedbackConstants
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -17,11 +18,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.rosetta.messenger.data.ForwardManager
import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.ui.components.AppleEmojiText
import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import java.util.*
import kotlinx.coroutines.launch
@@ -39,11 +44,13 @@ fun ForwardChatPickerBottomSheet(
dialogs: List<DialogUiModel>,
isDarkTheme: Boolean,
currentUserPublicKey: String,
avatarRepository: AvatarRepository? = null,
onDismiss: () -> Unit,
onChatSelected: (DialogUiModel) -> Unit
) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false)
val scope = rememberCoroutineScope()
val view = LocalView.current
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White
val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -53,6 +60,11 @@ fun ForwardChatPickerBottomSheet(
val forwardMessages by ForwardManager.forwardMessages.collectAsState()
val messagesCount = forwardMessages.size
// 🔥 Haptic feedback при открытии
LaunchedEffect(Unit) {
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
// 🔥 Функция для красивого закрытия с анимацией
fun dismissWithAnimation() {
scope.launch {
@@ -65,6 +77,7 @@ fun ForwardChatPickerBottomSheet(
onDismissRequest = { dismissWithAnimation() },
sheetState = sheetState,
containerColor = backgroundColor,
scrimColor = Color.Black.copy(alpha = 0.6f), // 🔥 Более тёмный overlay - перекрывает status bar
dragHandle = {
// Кастомный handle
Column(
@@ -85,7 +98,8 @@ fun ForwardChatPickerBottomSheet(
Spacer(modifier = Modifier.height(16.dp))
}
},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
modifier = Modifier.statusBarsPadding() // 🔥 Учитываем status bar
) {
Column(modifier = Modifier.fillMaxWidth().navigationBarsPadding()) {
// Header
@@ -170,6 +184,7 @@ fun ForwardChatPickerBottomSheet(
dialog = dialog,
isDarkTheme = isDarkTheme,
isSavedMessages = dialog.opponentKey == currentUserPublicKey,
avatarRepository = avatarRepository,
onClick = { onChatSelected(dialog) }
)
@@ -197,16 +212,12 @@ private fun ForwardDialogItem(
dialog: DialogUiModel,
isDarkTheme: Boolean,
isSavedMessages: Boolean = false,
avatarRepository: AvatarRepository? = null,
onClick: () -> Unit
) {
val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val avatarColors =
remember(dialog.opponentKey, isDarkTheme) {
getAvatarColor(dialog.opponentKey, isDarkTheme)
}
val displayName =
remember(dialog.opponentTitle, dialog.opponentUsername, dialog.opponentKey, isSavedMessages) {
when {
@@ -217,21 +228,14 @@ private fun ForwardDialogItem(
}
}
val initials =
remember(dialog.opponentTitle, dialog.opponentUsername, dialog.opponentKey, isSavedMessages) {
when {
isSavedMessages -> "📁"
dialog.opponentTitle.isNotEmpty() -> {
dialog.opponentTitle
.split(" ")
.take(2)
.mapNotNull { it.firstOrNull()?.uppercase() }
.joinToString("")
}
dialog.opponentUsername.isNotEmpty() -> dialog.opponentUsername.take(2).uppercase()
else -> dialog.opponentKey.take(2).uppercase()
}
}
// Display name for avatar initials
val avatarDisplayName = remember(dialog.opponentTitle, dialog.opponentUsername) {
when {
dialog.opponentTitle.isNotEmpty() -> dialog.opponentTitle
dialog.opponentUsername.isNotEmpty() -> dialog.opponentUsername
else -> null
}
}
Row(
modifier =
@@ -240,22 +244,34 @@ private fun ForwardDialogItem(
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Avatar
Box(
modifier =
Modifier.size(48.dp)
.clip(CircleShape)
.background(
if (isSavedMessages) PrimaryBlue.copy(alpha = 0.15f)
else avatarColors.backgroundColor
),
contentAlignment = Alignment.Center
) {
Text(
text = initials,
color = if (isSavedMessages) PrimaryBlue else avatarColors.textColor,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
// Avatar with real image support
if (isSavedMessages) {
// Saved Messages - special icon
val avatarColors = remember(dialog.opponentKey, isDarkTheme) {
getAvatarColor(dialog.opponentKey, isDarkTheme)
}
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(PrimaryBlue.copy(alpha = 0.15f)),
contentAlignment = Alignment.Center
) {
Text(
text = "📁",
color = PrimaryBlue,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
)
}
} else {
// Regular user - use AvatarImage with real avatar support
AvatarImage(
publicKey = dialog.opponentKey,
avatarRepository = avatarRepository,
size = 48.dp,
isDarkTheme = isDarkTheme,
displayName = avatarDisplayName
)
}
@@ -274,14 +290,23 @@ private fun ForwardDialogItem(
Spacer(modifier = Modifier.height(2.dp))
Text(
text =
if (isSavedMessages) "Your personal notes"
else dialog.lastMessage.ifEmpty { "No messages" },
// 📎 Определяем текст превью с учетом attachments
val previewText = when {
isSavedMessages -> "Your personal notes"
dialog.lastMessageAttachmentType == "Photo" -> "Photo"
dialog.lastMessageAttachmentType == "File" -> "File"
dialog.lastMessageAttachmentType == "Avatar" -> "Avatar"
dialog.lastMessageAttachmentType == "Forwarded" -> "Forwarded message"
dialog.lastMessage.isNotEmpty() -> dialog.lastMessage
else -> "No messages"
}
AppleEmojiText(
text = previewText,
fontSize = 14.sp,
color = secondaryTextColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
enableLinks = false
)
}