android: redesign chats search as fullscreen telegram-like flow
This commit is contained in:
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
@@ -34,6 +35,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -70,6 +72,7 @@ import androidx.compose.material.icons.filled.NotificationsOff
|
||||
import androidx.compose.material.icons.filled.PushPin
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import coil.compose.AsyncImage
|
||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||
@@ -231,7 +234,7 @@ fun ChatListScreen(
|
||||
Text(
|
||||
when {
|
||||
selectedChatIds.isNotEmpty() -> selectedChatIds.size.toString()
|
||||
isSearchMode -> "Search"
|
||||
isSearchMode -> ""
|
||||
state.isConnecting -> "Connecting..."
|
||||
state.selectedTab == ChatTab.ARCHIVED -> "Archived"
|
||||
else -> "Chats"
|
||||
@@ -311,14 +314,7 @@ fun ChatListScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (isSearchMode) {
|
||||
IconButton(onClick = { localSearchQuery = "" }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Close,
|
||||
contentDescription = "Clear search",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
} else if (!isSearchMode) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
isSearchMode = true
|
||||
@@ -394,119 +390,6 @@ fun ChatListScreen(
|
||||
}
|
||||
},
|
||||
)
|
||||
if (isSearchMode) {
|
||||
OutlinedTextField(
|
||||
value = localSearchQuery,
|
||||
onValueChange = {
|
||||
localSearchQuery = it
|
||||
onSearchChanged(it)
|
||||
onGlobalSearchChanged(it)
|
||||
},
|
||||
label = { Text(text = "Search chats") },
|
||||
singleLine = true,
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Search,
|
||||
contentDescription = "Search",
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
SearchSection.entries.forEach { section ->
|
||||
FilterChip(
|
||||
label = section.label,
|
||||
selected = searchSection == section,
|
||||
onClick = { searchSection = section },
|
||||
)
|
||||
}
|
||||
}
|
||||
if (searchSection == SearchSection.Chats) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
state.chats.take(8).forEach { chat ->
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.widthIn(max = 72.dp),
|
||||
) {
|
||||
if (!chat.avatarUrl.isNullOrBlank()) {
|
||||
AsyncImage(
|
||||
model = chat.avatarUrl,
|
||||
contentDescription = "Recent ${chat.displayTitle}",
|
||||
modifier = Modifier
|
||||
.size(56.dp)
|
||||
.clip(CircleShape),
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(56.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(chat.displayTitle.firstOrNull()?.uppercase() ?: "?")
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = chat.displayTitle,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.globalSearchQuery.trim().length >= 2) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.75f),
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
if (state.globalUsers.isNotEmpty()) {
|
||||
Text("Users", fontWeight = FontWeight.SemiBold)
|
||||
state.globalUsers.take(5).forEach { user ->
|
||||
Text("• ${user.name}${user.username?.let { " (@$it)" } ?: ""}")
|
||||
}
|
||||
}
|
||||
if (state.globalMessages.isNotEmpty()) {
|
||||
Text("Messages", fontWeight = FontWeight.SemiBold)
|
||||
state.globalMessages.take(5).forEach { message ->
|
||||
Text(
|
||||
text = "• [chat ${message.chatId}] ${(message.text ?: "[${message.type}]").take(60)}",
|
||||
modifier = Modifier.clickable { onOpenChat(message.chatId) },
|
||||
)
|
||||
}
|
||||
}
|
||||
if (state.globalUsers.isEmpty() && state.globalMessages.isEmpty()) {
|
||||
Text("No global results")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isSearchMode) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -536,73 +419,85 @@ fun ChatListScreen(
|
||||
onClick = { onFilterSelected(ChatListFilter.CHANNELS) },
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
PullToRefreshBox(
|
||||
isRefreshing = state.isRefreshing,
|
||||
onRefresh = onRefresh,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
when {
|
||||
state.isLoading -> {
|
||||
CenterState(text = "Loading chats...", loading = true)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
PullToRefreshBox(
|
||||
isRefreshing = state.isRefreshing,
|
||||
onRefresh = onRefresh,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
when {
|
||||
state.isLoading -> {
|
||||
CenterState(text = "Loading chats...", loading = true)
|
||||
}
|
||||
!state.errorMessage.isNullOrBlank() -> {
|
||||
CenterState(text = state.errorMessage, loading = false)
|
||||
}
|
||||
|
||||
!state.errorMessage.isNullOrBlank() -> {
|
||||
CenterState(text = state.errorMessage, loading = false)
|
||||
}
|
||||
state.chats.isEmpty() -> {
|
||||
CenterState(text = "No chats found", loading = false)
|
||||
}
|
||||
|
||||
state.chats.isEmpty() -> {
|
||||
CenterState(text = "No chats found", loading = false)
|
||||
}
|
||||
|
||||
else -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
) {
|
||||
if (state.selectedTab == ChatTab.ALL && state.archivedChatsCount > 0) {
|
||||
item(key = "archive_row") {
|
||||
ArchiveRow(
|
||||
count = state.archivedChatsCount,
|
||||
unreadCount = state.archivedUnreadCount,
|
||||
onClick = { onTabSelected(ChatTab.ARCHIVED) },
|
||||
)
|
||||
else -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
) {
|
||||
if (state.selectedTab == ChatTab.ALL && state.archivedChatsCount > 0) {
|
||||
item(key = "archive_row") {
|
||||
ArchiveRow(
|
||||
count = state.archivedChatsCount,
|
||||
unreadCount = state.archivedUnreadCount,
|
||||
onClick = { onTabSelected(ChatTab.ARCHIVED) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = state.chats,
|
||||
key = { it.id },
|
||||
) { chat ->
|
||||
ChatRow(
|
||||
chat = chat,
|
||||
isSelecting = selectedChatIds.isNotEmpty(),
|
||||
isSelected = selectedChatIds.contains(chat.id),
|
||||
onClick = {
|
||||
if (selectedChatIds.isNotEmpty()) {
|
||||
items(
|
||||
items = state.chats,
|
||||
key = { it.id },
|
||||
) { chat ->
|
||||
ChatRow(
|
||||
chat = chat,
|
||||
isSelecting = selectedChatIds.isNotEmpty(),
|
||||
isSelected = selectedChatIds.contains(chat.id),
|
||||
onClick = {
|
||||
if (selectedChatIds.isNotEmpty()) {
|
||||
selectedChatIds = if (selectedChatIds.contains(chat.id)) {
|
||||
selectedChatIds - chat.id
|
||||
} else {
|
||||
selectedChatIds + chat.id
|
||||
}
|
||||
} else {
|
||||
onOpenChat(chat.id)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
selectedChatIds = if (selectedChatIds.contains(chat.id)) {
|
||||
selectedChatIds - chat.id
|
||||
selectedChatIds
|
||||
} else {
|
||||
selectedChatIds + chat.id
|
||||
}
|
||||
} else {
|
||||
onOpenChat(chat.id)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
selectedChatIds = if (selectedChatIds.contains(chat.id)) {
|
||||
selectedChatIds
|
||||
} else {
|
||||
selectedChatIds + chat.id
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ChatSearchFullscreen(
|
||||
state = state,
|
||||
searchSection = searchSection,
|
||||
searchQuery = localSearchQuery,
|
||||
onSearchChanged = {
|
||||
localSearchQuery = it
|
||||
onSearchChanged(it)
|
||||
onGlobalSearchChanged(it)
|
||||
},
|
||||
onSectionChanged = { searchSection = it },
|
||||
onOpenChat = onOpenChat,
|
||||
)
|
||||
}
|
||||
if (managementExpanded) {
|
||||
Surface(
|
||||
@@ -790,6 +685,378 @@ fun ChatListScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChatSearchFullscreen(
|
||||
state: ChatListUiState,
|
||||
searchSection: SearchSection,
|
||||
searchQuery: String,
|
||||
onSearchChanged: (String) -> Unit,
|
||||
onSectionChanged: (SearchSection) -> Unit,
|
||||
onOpenChat: (Long) -> Unit,
|
||||
) {
|
||||
val trimmedQuery = searchQuery.trim()
|
||||
val sectionChats = remember(state.chats, searchSection) {
|
||||
when (searchSection) {
|
||||
SearchSection.Chats -> state.chats
|
||||
SearchSection.Channels -> state.chats.filter { it.type.equals("channel", ignoreCase = true) }
|
||||
SearchSection.Apps -> emptyList()
|
||||
SearchSection.Posts -> emptyList()
|
||||
}
|
||||
}
|
||||
val localQueryResults = remember(sectionChats, trimmedQuery) {
|
||||
if (trimmedQuery.isBlank()) {
|
||||
emptyList()
|
||||
} else {
|
||||
sectionChats.filter {
|
||||
it.displayTitle.contains(trimmedQuery, ignoreCase = true) ||
|
||||
(it.counterpartUsername?.contains(trimmedQuery, ignoreCase = true) == true) ||
|
||||
(it.handle?.contains(trimmedQuery, ignoreCase = true) == true) ||
|
||||
(it.lastMessageText?.contains(trimmedQuery, ignoreCase = true) == true)
|
||||
}
|
||||
}
|
||||
}
|
||||
val recentCircleChats = remember(sectionChats) { sectionChats.take(8) }
|
||||
val recentHistoryChats = remember(sectionChats) { sectionChats.take(14) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = searchQuery,
|
||||
onValueChange = onSearchChanged,
|
||||
placeholder = { Text("Поиск чатов") },
|
||||
singleLine = true,
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Search,
|
||||
contentDescription = "Search",
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (searchQuery.isNotBlank()) {
|
||||
IconButton(onClick = { onSearchChanged("") }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Clear,
|
||||
contentDescription = "Clear",
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = OutlinedTextFieldDefaults.colors(),
|
||||
shape = RoundedCornerShape(22.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(top = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
SearchSection.entries.forEach { section ->
|
||||
FilterChip(
|
||||
label = section.label,
|
||||
selected = searchSection == section,
|
||||
onClick = { onSectionChanged(section) },
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (trimmedQuery.isBlank()) {
|
||||
if (recentCircleChats.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(bottom = 10.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
recentCircleChats.forEach { chat ->
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.widthIn(max = 74.dp)
|
||||
.clickable { onOpenChat(chat.id) },
|
||||
) {
|
||||
ChatAvatar(chat = chat, size = 56.dp)
|
||||
Text(
|
||||
text = chat.displayTitle,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 6.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Недавние",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Text(
|
||||
text = "Очистить",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
items(recentHistoryChats, key = { "recent_${it.id}" }) { chat ->
|
||||
SearchChatRow(chat = chat, onClick = { onOpenChat(chat.id) })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
if (localQueryResults.isNotEmpty()) {
|
||||
items(localQueryResults.take(6), key = { "local_${it.id}" }) { chat ->
|
||||
SearchChatRow(chat = chat, onClick = { onOpenChat(chat.id) })
|
||||
}
|
||||
}
|
||||
item(key = "global_header") {
|
||||
SectionHeader(
|
||||
title = "Глобальный поиск",
|
||||
action = "Показать больше",
|
||||
)
|
||||
}
|
||||
if (state.globalUsers.isNotEmpty()) {
|
||||
items(state.globalUsers.take(8), key = { "user_${it.id}" }) { user ->
|
||||
SearchUserRow(
|
||||
title = user.name,
|
||||
subtitle = buildString {
|
||||
append("@")
|
||||
append(user.username ?: user.id.toString())
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
item(key = "global_empty") {
|
||||
Text(
|
||||
text = "Нет результатов",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 10.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
item(key = "messages_header") {
|
||||
SectionHeader(
|
||||
title = "Сообщения",
|
||||
action = "Из всех чатов",
|
||||
)
|
||||
}
|
||||
items(state.globalMessages.take(12), key = { "msg_${it.id}" }) { message ->
|
||||
SearchMessageRow(
|
||||
state = state,
|
||||
messageText = message.text?.take(70).orEmpty().ifBlank { "[${message.type}]" },
|
||||
time = formatChatTime(message.createdAt),
|
||||
onClick = { onOpenChat(message.chatId) },
|
||||
chatId = message.chatId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SectionHeader(
|
||||
title: String,
|
||||
action: String,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp, bottom = 4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Text(
|
||||
text = action,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchChatRow(
|
||||
chat: ChatItem,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
ChatAvatar(chat = chat, size = 48.dp)
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = chat.displayTitle,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
val subtitle = when {
|
||||
chat.type.equals("private", ignoreCase = true) -> chat.counterpartUsername?.let { "@$it" } ?: "private"
|
||||
chat.type.equals("channel", ignoreCase = true) -> "канал"
|
||||
else -> "группа"
|
||||
}
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
if (chat.unreadCount > 0) {
|
||||
BadgeChip(label = chat.unreadCount.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchUserRow(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = title.firstOrNull()?.uppercase() ?: "?",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchMessageRow(
|
||||
state: ChatListUiState,
|
||||
messageText: String,
|
||||
time: String,
|
||||
chatId: Long,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val chat = state.chats.firstOrNull { it.id == chatId }
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
if (chat != null) {
|
||||
ChatAvatar(chat = chat, size = 48.dp)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text("M")
|
||||
}
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = chat?.displayTitle ?: "Chat $chatId",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = messageText,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = time,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChatAvatar(
|
||||
chat: ChatItem,
|
||||
size: androidx.compose.ui.unit.Dp,
|
||||
) {
|
||||
if (!chat.avatarUrl.isNullOrBlank()) {
|
||||
AsyncImage(
|
||||
model = chat.avatarUrl,
|
||||
contentDescription = "Avatar for ${chat.displayTitle}",
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.clip(CircleShape),
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = chat.displayTitle.firstOrNull()?.uppercase() ?: "?",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun ChatRow(
|
||||
|
||||
Reference in New Issue
Block a user