Improve message forwarding picker
This commit is contained in:
@@ -2088,14 +2088,16 @@ private fun ChatScreen(vm: QMaxViewModel) {
|
|||||||
onClose = { materialsOpen = false }
|
onClose = { materialsOpen = false }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.forwardTarget != null) {
|
val forwardTarget = state.forwardTarget
|
||||||
|
if (forwardTarget != null) {
|
||||||
ForwardMessageDialog(
|
ForwardMessageDialog(
|
||||||
|
message = forwardTarget,
|
||||||
chats = state.chats,
|
chats = state.chats,
|
||||||
selectedChatId = chat.id,
|
selectedChatId = chat.id,
|
||||||
session = session,
|
session = session,
|
||||||
cachedAvatarPaths = state.cachedAvatarPaths,
|
cachedAvatarPaths = state.cachedAvatarPaths,
|
||||||
onCacheAvatar = vm::cacheAvatar,
|
onCacheAvatar = vm::cacheAvatar,
|
||||||
onSelect = vm::forwardTo,
|
onSend = vm::forwardTo,
|
||||||
onClose = vm::closeForwardPicker
|
onClose = vm::closeForwardPicker
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2103,12 +2105,13 @@ private fun ChatScreen(vm: QMaxViewModel) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ForwardMessageDialog(
|
private fun ForwardMessageDialog(
|
||||||
|
message: MessageDto,
|
||||||
chats: List<ChatDto>,
|
chats: List<ChatDto>,
|
||||||
selectedChatId: String?,
|
selectedChatId: String?,
|
||||||
session: QMaxSession?,
|
session: QMaxSession?,
|
||||||
cachedAvatarPaths: Map<String, String>,
|
cachedAvatarPaths: Map<String, String>,
|
||||||
onCacheAvatar: (String) -> Unit,
|
onCacheAvatar: (String) -> Unit,
|
||||||
onSelect: (ChatDto) -> Unit,
|
onSend: (List<ChatDto>) -> Unit,
|
||||||
onClose: () -> Unit
|
onClose: () -> Unit
|
||||||
) {
|
) {
|
||||||
val sortedChats = remember(chats) {
|
val sortedChats = remember(chats) {
|
||||||
@@ -2117,30 +2120,70 @@ private fun ForwardMessageDialog(
|
|||||||
.thenByDescending { it.lastMessageAt.orEmpty() }
|
.thenByDescending { it.lastMessageAt.orEmpty() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
var query by rememberSaveable { mutableStateOf("") }
|
||||||
|
var selectedChatIds by remember { mutableStateOf(emptySet<String>()) }
|
||||||
|
val filteredChats = remember(sortedChats, query) {
|
||||||
|
val term = query.trim()
|
||||||
|
if (term.isBlank()) {
|
||||||
|
sortedChats
|
||||||
|
} else {
|
||||||
|
sortedChats.filter { chat ->
|
||||||
|
chat.title.contains(term, ignoreCase = true) ||
|
||||||
|
chat.lastMessagePreview?.contains(term, ignoreCase = true) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val selectedChats = remember(sortedChats, selectedChatIds) {
|
||||||
|
sortedChats.filter { it.id in selectedChatIds }
|
||||||
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onClose,
|
onDismissRequest = onClose,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onClose) {
|
Button(
|
||||||
Text("\u041e\u0442\u043c\u0435\u043d\u0430")
|
onClick = { onSend(selectedChats) },
|
||||||
|
enabled = selectedChats.isNotEmpty()
|
||||||
|
) {
|
||||||
|
Icon(Icons.AutoMirrored.Filled.Send, contentDescription = null)
|
||||||
|
Spacer(Modifier.width(6.dp))
|
||||||
|
Text(if (selectedChats.size <= 1) "Отправить" else "Отправить (${selectedChats.size})")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = { Text("\u041f\u0435\u0440\u0435\u0441\u043b\u0430\u0442\u044c") },
|
dismissButton = {
|
||||||
|
TextButton(onClick = onClose) {
|
||||||
|
Text("Отмена")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = { Text("Переслать") },
|
||||||
text = {
|
text = {
|
||||||
if (sortedChats.isEmpty()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
EmptyState("\u0427\u0430\u0442\u043e\u0432 \u043f\u043e\u043a\u0430 \u043d\u0435\u0442", modifier = Modifier.height(220.dp))
|
ForwardMessagePreview(message)
|
||||||
} else {
|
Spacer(Modifier.height(10.dp))
|
||||||
LazyColumn(Modifier.fillMaxWidth().height(430.dp)) {
|
ForwardSearchField(query = query, onQuery = { query = it })
|
||||||
items(sortedChats, key = { it.id }) { chat ->
|
Spacer(Modifier.height(8.dp))
|
||||||
ForwardChatRow(
|
when {
|
||||||
chat = chat,
|
sortedChats.isEmpty() -> EmptyState("Чатов пока нет", modifier = Modifier.height(220.dp))
|
||||||
isCurrent = chat.id == selectedChatId,
|
filteredChats.isEmpty() -> EmptyState("Ничего не найдено", modifier = Modifier.height(220.dp))
|
||||||
session = session,
|
else -> LazyColumn(Modifier.fillMaxWidth().height(430.dp)) {
|
||||||
cachedAvatarPaths = cachedAvatarPaths,
|
items(filteredChats, key = { it.id }) { chat ->
|
||||||
onCacheAvatar = onCacheAvatar,
|
val selected = chat.id in selectedChatIds
|
||||||
onClick = { onSelect(chat) }
|
ForwardChatRow(
|
||||||
)
|
chat = chat,
|
||||||
HorizontalDivider(color = Color(0xFFE9EEF2), thickness = 0.6.dp, modifier = Modifier.padding(start = 56.dp))
|
isCurrent = chat.id == selectedChatId,
|
||||||
|
selected = selected,
|
||||||
|
session = session,
|
||||||
|
cachedAvatarPaths = cachedAvatarPaths,
|
||||||
|
onCacheAvatar = onCacheAvatar,
|
||||||
|
onClick = {
|
||||||
|
selectedChatIds = if (selected) {
|
||||||
|
selectedChatIds - chat.id
|
||||||
|
} else {
|
||||||
|
selectedChatIds + chat.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
HorizontalDivider(color = Color(0xFFE9EEF2), thickness = 0.6.dp, modifier = Modifier.padding(start = 56.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2152,6 +2195,7 @@ private fun ForwardMessageDialog(
|
|||||||
private fun ForwardChatRow(
|
private fun ForwardChatRow(
|
||||||
chat: ChatDto,
|
chat: ChatDto,
|
||||||
isCurrent: Boolean,
|
isCurrent: Boolean,
|
||||||
|
selected: Boolean,
|
||||||
session: QMaxSession?,
|
session: QMaxSession?,
|
||||||
cachedAvatarPaths: Map<String, String>,
|
cachedAvatarPaths: Map<String, String>,
|
||||||
onCacheAvatar: (String) -> Unit,
|
onCacheAvatar: (String) -> Unit,
|
||||||
@@ -2201,7 +2245,82 @@ private fun ForwardChatRow(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text(formatChatTime(chat.lastMessageAt), color = QMaxMuted, style = MaterialTheme.typography.labelSmall)
|
if (selected) {
|
||||||
|
Surface(color = QMaxBlue, shape = CircleShape, modifier = Modifier.size(26.dp)) {
|
||||||
|
Box(contentAlignment = Alignment.Center) {
|
||||||
|
Icon(Icons.Filled.Check, contentDescription = "Выбрано", tint = Color.White, modifier = Modifier.size(18.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(formatChatTime(chat.lastMessageAt), color = QMaxMuted, style = MaterialTheme.typography.labelSmall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ForwardMessagePreview(message: MessageDto) {
|
||||||
|
Surface(color = Color(0xFFE7F1FA), shape = RoundedCornerShape(10.dp)) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.width(3.dp)
|
||||||
|
.height(38.dp)
|
||||||
|
.background(QMaxBlue)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(9.dp))
|
||||||
|
Column(Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
replyPreviewAuthor(message),
|
||||||
|
color = QMaxBlue,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
replyPreviewBody(message),
|
||||||
|
color = QMaxMuted,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ForwardSearchField(query: String, onQuery: (String) -> Unit) {
|
||||||
|
Surface(color = Color(0xFFF1F1F3), shape = RoundedCornerShape(24.dp)) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(46.dp).padding(start = 14.dp, end = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(Icons.Filled.Search, contentDescription = null, tint = QMaxMuted, modifier = Modifier.size(23.dp))
|
||||||
|
TextField(
|
||||||
|
value = query,
|
||||||
|
onValueChange = onQuery,
|
||||||
|
placeholder = { Text("Кому переслать", color = Color(0xFF8A8E93)) },
|
||||||
|
singleLine = true,
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedTextColor = QMaxText,
|
||||||
|
unfocusedTextColor = QMaxText,
|
||||||
|
focusedContainerColor = Color.Transparent,
|
||||||
|
unfocusedContainerColor = Color.Transparent,
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
|
cursorColor = QMaxBlue
|
||||||
|
),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
if (query.isNotBlank()) {
|
||||||
|
IconButton(onClick = { onQuery("") }) {
|
||||||
|
Icon(Icons.Filled.Close, contentDescription = "Очистить поиск", tint = QMaxMuted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -894,15 +894,23 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
|
|||||||
state.value = state.value.copy(forwardTarget = null)
|
state.value = state.value.copy(forwardTarget = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forwardTo(chat: ChatDto) = launchLoading(false) {
|
fun forwardTo(targetChats: List<ChatDto>) = launchLoading(false) {
|
||||||
val session = requireSession()
|
val session = requireSession()
|
||||||
val source = state.value.forwardTarget ?: return@launchLoading
|
val source = state.value.forwardTarget ?: return@launchLoading
|
||||||
val forwarded = repository.forwardMessage(session, source.chatId, source.id, chat.id)
|
val targets = targetChats.distinctBy { it.id }
|
||||||
|
if (targets.isEmpty()) return@launchLoading
|
||||||
|
val forwardedMessages = targets.map { chat ->
|
||||||
|
repository.forwardMessage(session, source.chatId, source.id, chat.id)
|
||||||
|
}
|
||||||
val current = state.value
|
val current = state.value
|
||||||
state.value = if (current.selectedChat?.id == chat.id) {
|
val openedChatId = current.selectedChat?.id
|
||||||
|
val openedChatForwardedMessages = forwardedMessages.filter { it.chatId == openedChatId }
|
||||||
|
state.value = if (openedChatForwardedMessages.isNotEmpty()) {
|
||||||
current.copy(
|
current.copy(
|
||||||
forwardTarget = null,
|
forwardTarget = null,
|
||||||
messages = (current.messages.filterNot { it.id == forwarded.id } + forwarded).sortedBy { it.sentAt }
|
messages = (current.messages.filterNot { existing ->
|
||||||
|
openedChatForwardedMessages.any { it.id == existing.id }
|
||||||
|
} + openedChatForwardedMessages).sortedBy { it.sentAt }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
current.copy(forwardTarget = null)
|
current.copy(forwardTarget = null)
|
||||||
|
|||||||
Reference in New Issue
Block a user