feat: add private chat info card
Show a compact Telegram-like counterpart card inside private chats. Reuse existing chat data for avatar, username, and status instead of adding new profile fetches. Display contact and blocked badges when the relationship state is already known.
This commit is contained in:
@@ -612,6 +612,7 @@ private fun ChatScreen(
|
||||
state.isCounterpartRelationshipResolved &&
|
||||
!state.isCounterpartContact &&
|
||||
!state.isCounterpartBlocked
|
||||
val showPrivateInfoCard = isPrivateChat && state.counterpartUserId != null
|
||||
val canShowMembersTab = state.chatType.equals("group", ignoreCase = true) ||
|
||||
(state.chatType.equals("channel", ignoreCase = true) && state.canManageMembers)
|
||||
val timelineItems = remember(state.messages, context) { buildChatTimelineItems(state.messages, context) }
|
||||
@@ -1165,6 +1166,26 @@ private fun ChatScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = showPrivateInfoCard,
|
||||
enter = fadeIn(animationSpec = tween(180)) + slideInVertically(
|
||||
initialOffsetY = { -it / 4 },
|
||||
animationSpec = tween(180),
|
||||
),
|
||||
exit = fadeOut(animationSpec = tween(120)) + slideOutVertically(
|
||||
targetOffsetY = { -it / 4 },
|
||||
animationSpec = tween(120),
|
||||
),
|
||||
) {
|
||||
PrivateChatInfoCard(
|
||||
title = state.counterpartName?.takeIf { it.isNotBlank() } ?: state.chatTitle,
|
||||
username = state.counterpartUsername,
|
||||
avatarUrl = state.chatAvatarUrl,
|
||||
statusText = state.baseChatSubtitle.takeIf { it.isNotBlank() },
|
||||
isContact = state.isCounterpartContact,
|
||||
isBlocked = state.isCounterpartBlocked,
|
||||
)
|
||||
}
|
||||
val strip = topAudioStrip
|
||||
if (strip != null) {
|
||||
Row(
|
||||
@@ -2242,6 +2263,103 @@ private enum class ChatViewerMediaType {
|
||||
Video,
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrivateChatInfoCard(
|
||||
title: String,
|
||||
username: String?,
|
||||
avatarUrl: String?,
|
||||
statusText: String?,
|
||||
isContact: Boolean,
|
||||
isBlocked: Boolean,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.78f),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (!avatarUrl.isNullOrBlank()) {
|
||||
AsyncImage(
|
||||
model = avatarUrl,
|
||||
contentDescription = "Private chat avatar",
|
||||
modifier = Modifier
|
||||
.size(54.dp)
|
||||
.clip(CircleShape),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(54.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.18f)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = title.firstOrNull()?.uppercase() ?: "?",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title.ifBlank { stringResource(id = R.string.chat_unknown_user) },
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
username?.takeIf { it.isNotBlank() }?.let {
|
||||
Text(
|
||||
text = "@$it",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
statusText?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End,
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
if (isContact) {
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
label = { Text(stringResource(id = R.string.chat_private_info_contact)) },
|
||||
)
|
||||
}
|
||||
if (isBlocked) {
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
label = { Text(stringResource(id = R.string.chat_private_info_blocked)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class ChatViewerMediaItem(
|
||||
val url: String,
|
||||
val type: ChatViewerMediaType,
|
||||
|
||||
@@ -778,6 +778,10 @@ class ChatViewModel @Inject constructor(
|
||||
it.copy(
|
||||
chatType = chat.type,
|
||||
counterpartUserId = chat.counterpartUserId,
|
||||
counterpartName = chat.counterpartName,
|
||||
counterpartUsername = chat.counterpartUsername,
|
||||
counterpartIsOnline = chat.counterpartIsOnline,
|
||||
counterpartLastSeenAt = chat.counterpartLastSeenAt,
|
||||
isCounterpartRelationshipResolved = if (chat.type.equals("private", ignoreCase = true) && chat.counterpartUserId != null) {
|
||||
it.isCounterpartRelationshipResolved
|
||||
} else {
|
||||
|
||||
@@ -19,6 +19,10 @@ data class MessageUiState(
|
||||
val chatAvatarUrl: String? = null,
|
||||
val chatType: String = "",
|
||||
val counterpartUserId: Long? = null,
|
||||
val counterpartName: String? = null,
|
||||
val counterpartUsername: String? = null,
|
||||
val counterpartIsOnline: Boolean? = null,
|
||||
val counterpartLastSeenAt: String? = null,
|
||||
val chatRole: String? = null,
|
||||
val chatMuted: Boolean = false,
|
||||
val chatUnreadCount: Int = 0,
|
||||
|
||||
@@ -142,6 +142,8 @@
|
||||
<string name="chat_unknown_user_banner_subtitle">Можно добавить этого человека в контакты или заблокировать прямо из чата.</string>
|
||||
<string name="chat_unknown_user_add_contact">Добавить контакт</string>
|
||||
<string name="chat_unknown_user_block">Заблокировать</string>
|
||||
<string name="chat_private_info_contact">Контакт</string>
|
||||
<string name="chat_private_info_blocked">Заблокирован</string>
|
||||
<string name="chat_type_group">группа</string>
|
||||
<string name="chat_type_channel">канал</string>
|
||||
<string name="chat_info_tab_media">Медиа</string>
|
||||
|
||||
@@ -142,6 +142,8 @@
|
||||
<string name="chat_unknown_user_banner_subtitle">You can add this person to contacts or block them right from the chat.</string>
|
||||
<string name="chat_unknown_user_add_contact">Add contact</string>
|
||||
<string name="chat_unknown_user_block">Block user</string>
|
||||
<string name="chat_private_info_contact">Contact</string>
|
||||
<string name="chat_private_info_blocked">Blocked</string>
|
||||
<string name="chat_type_group">group</string>
|
||||
<string name="chat_type_channel">channel</string>
|
||||
<string name="chat_info_tab_media">Media</string>
|
||||
|
||||
Reference in New Issue
Block a user