sync fix and reactions
This commit is contained in:
@@ -13,7 +13,9 @@
|
||||
"WebFetch(domain:raw.githubusercontent.com)",
|
||||
"Bash(export TERM=dumb:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(jar tf:*)"
|
||||
"Bash(jar tf:*)",
|
||||
"Bash(javap:*)",
|
||||
"Bash(jar xf:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ fun MainScreen(
|
||||
val expandedThreadRooms by viewModel.expandedThreadRooms.collectAsStateWithLifecycle()
|
||||
val selectedThread by viewModel.selectedThread.collectAsStateWithLifecycle()
|
||||
val threadMessages by viewModel.threadMessages.collectAsStateWithLifecycle()
|
||||
val currentUserId by viewModel.currentUserId.collectAsStateWithLifecycle()
|
||||
val replyingTo by viewModel.replyingTo.collectAsStateWithLifecycle()
|
||||
val editingMessage by viewModel.editingMessage.collectAsStateWithLifecycle()
|
||||
val listState = viewModel.channelListState
|
||||
val preferencesManager: PreferencesManager = koinInject()
|
||||
val hideSpacesWhenClosed by preferencesManager.hideSpacesWhenClosed.collectAsStateWithLifecycle(initialValue = false)
|
||||
@@ -128,6 +131,17 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
},
|
||||
currentUserId = currentUserId,
|
||||
replyingTo = replyingTo,
|
||||
editingMessage = editingMessage,
|
||||
onSetReplyingTo = { viewModel.setReplyingTo(it) },
|
||||
onSetEditingMessage = { viewModel.setEditingMessage(it) },
|
||||
onSendReply = { body, eventId -> viewModel.sendReply(body, eventId) },
|
||||
onSendThreadReply = { body, eventId -> viewModel.sendThreadReply(body, eventId) },
|
||||
onEditMessage = { eventId, body -> viewModel.editMessage(eventId, body) },
|
||||
onEditThreadMessage = { eventId, body -> viewModel.editThreadMessage(eventId, body) },
|
||||
onSendReaction = { eventId, emoji -> viewModel.sendReaction(eventId, emoji) },
|
||||
onSendThreadReaction = { eventId, emoji -> viewModel.sendThreadReaction(eventId, emoji) },
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = showMemberList) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.EditedContent
|
||||
import org.matrix.rustcomponents.sdk.EventOrTransactionId
|
||||
import org.matrix.rustcomponents.sdk.Membership
|
||||
import org.matrix.rustcomponents.sdk.MembershipState
|
||||
@@ -185,6 +186,15 @@ class MainViewModel(
|
||||
|
||||
private val _homeUnreadStatus = MutableStateFlow(UnreadStatus.NONE)
|
||||
val homeUnreadStatus: StateFlow<UnreadStatus> = _homeUnreadStatus
|
||||
|
||||
private val _currentUserId = MutableStateFlow<String?>(null)
|
||||
val currentUserId: StateFlow<String?> = _currentUserId
|
||||
|
||||
private val _replyingTo = MutableStateFlow<MessageItem?>(null)
|
||||
val replyingTo: StateFlow<MessageItem?> = _replyingTo
|
||||
|
||||
private val _editingMessage = MutableStateFlow<MessageItem?>(null)
|
||||
val editingMessage: StateFlow<MessageItem?> = _editingMessage
|
||||
private val _spaceChildrenMap = MutableStateFlow<Map<String, Set<String>>>(emptyMap())
|
||||
|
||||
private val _channelSections = MutableStateFlow<List<ChannelSection>>(emptyList())
|
||||
@@ -271,6 +281,7 @@ class MainViewModel(
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
syncService = authRepository.getOrStartSync()
|
||||
try { _currentUserId.value = authRepository.getClient()?.userId() } catch (_: Exception) {}
|
||||
loadRooms()
|
||||
}
|
||||
observeSelectedChannel()
|
||||
@@ -545,11 +556,46 @@ class MainViewModel(
|
||||
}
|
||||
is TimelineDiff.Set -> {
|
||||
val idx = d.index.toInt()
|
||||
if (idx in sdkItems.indices) sdkItems[idx] = d.value
|
||||
if (idx in sdkItems.indices) {
|
||||
// Remove the old item from cache (e.g. TransactionId → EventId transition on send)
|
||||
val old = sdkItems[idx]
|
||||
sdkItems[idx] = d.value
|
||||
val oldEvent = old.asEvent()
|
||||
if (oldEvent != null) {
|
||||
val oldId = when (val eot = oldEvent.eventOrTransactionId) {
|
||||
is EventOrTransactionId.EventId -> eot.eventId
|
||||
is EventOrTransactionId.TransactionId -> eot.transactionId
|
||||
}
|
||||
if (ids.remove(oldId)) {
|
||||
val ri = cached.indexOfFirst { it.eventId == oldId }
|
||||
if (ri >= 0) cached.removeAt(ri)
|
||||
// Also remove from thread cache if it was a thread reply
|
||||
threadMessageCache[roomId]?.values?.forEach { list ->
|
||||
list.removeAll { it.eventId == oldId }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TimelineDiff.Remove -> {
|
||||
val idx = d.index.toInt()
|
||||
if (idx in sdkItems.indices) sdkItems.removeAt(idx)
|
||||
if (idx in sdkItems.indices) {
|
||||
val removed = sdkItems.removeAt(idx)
|
||||
val removedEvent = removed.asEvent()
|
||||
if (removedEvent != null) {
|
||||
val removedId = when (val eot = removedEvent.eventOrTransactionId) {
|
||||
is EventOrTransactionId.EventId -> eot.eventId
|
||||
is EventOrTransactionId.TransactionId -> eot.transactionId
|
||||
}
|
||||
if (ids.remove(removedId)) {
|
||||
val ri = cached.indexOfFirst { it.eventId == removedId }
|
||||
if (ri >= 0) cached.removeAt(ri)
|
||||
threadMessageCache[roomId]?.values?.forEach { list ->
|
||||
list.removeAll { it.eventId == removedId }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TimelineDiff.Truncate -> {
|
||||
val len = d.length.toInt()
|
||||
@@ -1442,6 +1488,71 @@ class MainViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun setReplyingTo(message: MessageItem?) { _replyingTo.value = message }
|
||||
fun setEditingMessage(message: MessageItem?) { _editingMessage.value = message }
|
||||
|
||||
fun editMessage(eventId: String, newBody: String) {
|
||||
val timeline = activeTimeline ?: return
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
timeline.edit(
|
||||
EventOrTransactionId.EventId(eventId),
|
||||
EditedContent.RoomMessage(messageEventContentFromMarkdown(newBody)),
|
||||
)
|
||||
} catch (_: Exception) {}
|
||||
_editingMessage.value = null
|
||||
}
|
||||
}
|
||||
|
||||
fun editThreadMessage(eventId: String, newBody: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
threadTimelineMutex.withLock {
|
||||
val t = activeThreadTimeline ?: return@withLock
|
||||
try {
|
||||
t.edit(
|
||||
EventOrTransactionId.EventId(eventId),
|
||||
EditedContent.RoomMessage(messageEventContentFromMarkdown(newBody)),
|
||||
)
|
||||
} catch (_: Exception) {}
|
||||
_editingMessage.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendReaction(eventId: String, emoji: String) {
|
||||
val timeline = activeTimeline ?: return
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try { timeline.toggleReaction(EventOrTransactionId.EventId(eventId), emoji) } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendThreadReaction(eventId: String, emoji: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
threadTimelineMutex.withLock {
|
||||
val t = activeThreadTimeline ?: return@withLock
|
||||
try { t.toggleReaction(EventOrTransactionId.EventId(eventId), emoji) } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendReply(body: String, replyToEventId: String) {
|
||||
val timeline = activeTimeline ?: return
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try { timeline.sendReply(messageEventContentFromMarkdown(body), replyToEventId) } catch (_: Exception) {}
|
||||
_replyingTo.value = null
|
||||
}
|
||||
}
|
||||
|
||||
fun sendThreadReply(body: String, replyToEventId: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
threadTimelineMutex.withLock {
|
||||
val t = activeThreadTimeline ?: return@withLock
|
||||
try { t.sendReply(messageEventContentFromMarkdown(body), replyToEventId) } catch (_: Exception) {}
|
||||
_replyingTo.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
ProcessLifecycleOwner.get().lifecycle.removeObserver(lifecycleObserver)
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -55,15 +55,23 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.TextButton
|
||||
import com.mikepenz.markdown.m3.Markdown
|
||||
import com.mikepenz.markdown.m3.markdownColor
|
||||
import com.mikepenz.markdown.m3.markdownTypography
|
||||
import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.AttachFile
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.automirrored.filled.Reply
|
||||
import androidx.compose.material.icons.filled.PlayCircleFilled
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
@@ -140,9 +148,21 @@ fun MessageTimeline(
|
||||
onOpenThread: (String) -> Unit = {},
|
||||
threadReplyCounts: Map<String, Int> = emptyMap(),
|
||||
selectedThreadName: String? = null,
|
||||
currentUserId: String? = null,
|
||||
replyingTo: MessageItem? = null,
|
||||
editingMessage: MessageItem? = null,
|
||||
onSetReplyingTo: (MessageItem?) -> Unit = {},
|
||||
onSetEditingMessage: (MessageItem?) -> Unit = {},
|
||||
onSendReply: (String, String) -> Unit = { _, _ -> },
|
||||
onSendThreadReply: (String, String) -> Unit = { _, _ -> },
|
||||
onEditMessage: (String, String) -> Unit = { _, _ -> },
|
||||
onEditThreadMessage: (String, String) -> Unit = { _, _ -> },
|
||||
onSendReaction: (String, String) -> Unit = { _, _ -> },
|
||||
onSendThreadReaction: (String, String) -> Unit = { _, _ -> },
|
||||
) {
|
||||
var fullscreenImageUrl by remember { mutableStateOf<String?>(null) }
|
||||
var fullscreenVideoUrl by remember { mutableStateOf<String?>(null) }
|
||||
var contextMenuMessage by remember { mutableStateOf<MessageItem?>(null) }
|
||||
|
||||
if (fullscreenImageUrl != null) {
|
||||
FullscreenImageViewer(
|
||||
@@ -158,6 +178,32 @@ fun MessageTimeline(
|
||||
)
|
||||
}
|
||||
|
||||
contextMenuMessage?.let { msg ->
|
||||
MessageContextMenu(
|
||||
message = msg,
|
||||
isOwnMessage = msg.senderId == currentUserId,
|
||||
isInThread = selectedThread != null,
|
||||
onDismiss = { contextMenuMessage = null },
|
||||
onReact = { emoji ->
|
||||
contextMenuMessage = null
|
||||
if (selectedThread != null) onSendThreadReaction(msg.eventId, emoji)
|
||||
else onSendReaction(msg.eventId, emoji)
|
||||
},
|
||||
onReply = {
|
||||
onSetReplyingTo(msg)
|
||||
contextMenuMessage = null
|
||||
},
|
||||
onEdit = {
|
||||
onSetEditingMessage(msg)
|
||||
contextMenuMessage = null
|
||||
},
|
||||
onStartThread = {
|
||||
onOpenThread(msg.eventId)
|
||||
contextMenuMessage = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalImageViewer provides { url -> fullscreenImageUrl = url },
|
||||
LocalVideoPlayer provides { url -> fullscreenVideoUrl = url },
|
||||
@@ -281,17 +327,17 @@ fun MessageTimeline(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (isFirstInGroup) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
FullMessage(message, onOpenThread = onOpenThread, threadReplyCount = threadReplyCounts[message.eventId] ?: 0)
|
||||
FullMessage(message, onOpenThread = onOpenThread, threadReplyCount = threadReplyCounts[message.eventId] ?: 0, onLongPress = { contextMenuMessage = it })
|
||||
} else {
|
||||
CompactMessage(message)
|
||||
CompactMessage(message, onLongPress = { contextMenuMessage = it })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isFirstInGroup) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
FullMessage(message, onOpenThread = onOpenThread)
|
||||
FullMessage(message, onOpenThread = onOpenThread, onLongPress = { contextMenuMessage = it })
|
||||
} else {
|
||||
CompactMessage(message)
|
||||
CompactMessage(message, onLongPress = { contextMenuMessage = it })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,10 +365,41 @@ fun MessageTimeline(
|
||||
}
|
||||
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||
// Reply preview bar
|
||||
replyingTo?.let { replyMsg ->
|
||||
ReplyPreviewBar(
|
||||
senderName = replyMsg.senderName,
|
||||
body = when (val c = replyMsg.content) {
|
||||
is com.example.fluffytrix.ui.screens.main.MessageContent.Text -> c.body
|
||||
else -> "Attachment"
|
||||
},
|
||||
onDismiss = { onSetReplyingTo(null) },
|
||||
)
|
||||
}
|
||||
// Edit mode bar
|
||||
editingMessage?.let { editMsg ->
|
||||
EditModeBar(
|
||||
body = when (val c = editMsg.content) {
|
||||
is com.example.fluffytrix.ui.screens.main.MessageContent.Text -> c.body
|
||||
else -> ""
|
||||
},
|
||||
onDismiss = { onSetEditingMessage(null) },
|
||||
)
|
||||
}
|
||||
MessageInput(
|
||||
channelName = if (selectedThread != null) "thread" else (channelName ?: "message"),
|
||||
replyingTo = replyingTo,
|
||||
editingMessage = editingMessage,
|
||||
onSendMessage = activeSend,
|
||||
onSendFiles = onSendFiles,
|
||||
onSendReply = { body, eventId ->
|
||||
if (selectedThread != null) onSendThreadReply(body, eventId)
|
||||
else onSendReply(body, eventId)
|
||||
},
|
||||
onEditMessage = { eventId, body ->
|
||||
if (selectedThread != null) onEditThreadMessage(eventId, body)
|
||||
else onEditMessage(eventId, body)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -371,12 +448,12 @@ private fun ThreadTopBar(title: String, onClose: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FullMessage(message: MessageItem, onOpenThread: (String) -> Unit = {}, threadReplyCount: Int = 0) {
|
||||
private fun FullMessage(message: MessageItem, onOpenThread: (String) -> Unit = {}, threadReplyCount: Int = 0, onLongPress: (MessageItem) -> Unit = {}) {
|
||||
val senderColor = remember(message.senderName) { colorForSender(message.senderName) }
|
||||
val time = remember(message.timestamp) { formatTimestamp(message.timestamp) }
|
||||
val reply = message.replyTo
|
||||
|
||||
Column {
|
||||
Column(modifier = Modifier.combinedClickable(onClick = {}, onLongClick = { onLongPress(message) })) {
|
||||
if (reply != null) {
|
||||
ReplyConnector(reply, hasAvatar = true)
|
||||
}
|
||||
@@ -427,8 +504,8 @@ private fun FullMessage(message: MessageItem, onOpenThread: (String) -> Unit = {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CompactMessage(message: MessageItem) {
|
||||
Column {
|
||||
private fun CompactMessage(message: MessageItem, onLongPress: (MessageItem) -> Unit = {}) {
|
||||
Column(modifier = Modifier.combinedClickable(onClick = {}, onLongClick = { onLongPress(message) })) {
|
||||
if (message.replyTo != null) {
|
||||
ReplyConnector(message.replyTo, hasAvatar = false)
|
||||
}
|
||||
@@ -807,9 +884,27 @@ private fun formatFileSize(bytes: Long): String {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MessageInput(channelName: String, onSendMessage: (String) -> Unit, onSendFiles: (List<Uri>, String?) -> Unit) {
|
||||
private fun MessageInput(
|
||||
channelName: String,
|
||||
onSendMessage: (String) -> Unit,
|
||||
onSendFiles: (List<Uri>, String?) -> Unit,
|
||||
replyingTo: MessageItem? = null,
|
||||
editingMessage: MessageItem? = null,
|
||||
onSendReply: (String, String) -> Unit = { _, _ -> },
|
||||
onEditMessage: (String, String) -> Unit = { _, _ -> },
|
||||
) {
|
||||
var text by remember { mutableStateOf("") }
|
||||
var attachedUris by remember { mutableStateOf(listOf<Uri>()) }
|
||||
|
||||
// Pre-fill text when entering edit mode
|
||||
androidx.compose.runtime.LaunchedEffect(editingMessage) {
|
||||
if (editingMessage != null) {
|
||||
text = when (val c = editingMessage.content) {
|
||||
is com.example.fluffytrix.ui.screens.main.MessageContent.Text -> c.body
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
}
|
||||
val filePickerLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetMultipleContents()
|
||||
) { uris: List<Uri> ->
|
||||
@@ -887,22 +982,186 @@ private fun MessageInput(channelName: String, onSendMessage: (String) -> Unit, o
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (attachedUris.isNotEmpty()) {
|
||||
onSendFiles(attachedUris, text.trim().ifBlank { null })
|
||||
val trimmed = text.trim()
|
||||
when {
|
||||
editingMessage != null && trimmed.isNotBlank() -> {
|
||||
onEditMessage(editingMessage.eventId, trimmed)
|
||||
text = ""
|
||||
}
|
||||
replyingTo != null && trimmed.isNotBlank() -> {
|
||||
onSendReply(trimmed, replyingTo.eventId)
|
||||
text = ""
|
||||
}
|
||||
attachedUris.isNotEmpty() -> {
|
||||
onSendFiles(attachedUris, trimmed.ifBlank { null })
|
||||
attachedUris = emptyList()
|
||||
text = ""
|
||||
} else if (text.isNotBlank()) {
|
||||
onSendMessage(text.trim())
|
||||
}
|
||||
trimmed.isNotBlank() -> {
|
||||
onSendMessage(trimmed)
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = canSend,
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.Send, "Send",
|
||||
if (editingMessage != null) Icons.Default.Check else Icons.AutoMirrored.Filled.Send,
|
||||
if (editingMessage != null) "Confirm edit" else "Send",
|
||||
tint = if (canSend) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val QUICK_REACTIONS = listOf("👍", "👎", "❤️", "😂", "😮", "😢", "🎉", "🔥")
|
||||
|
||||
@Composable
|
||||
private fun MessageContextMenu(
|
||||
message: MessageItem,
|
||||
isOwnMessage: Boolean,
|
||||
isInThread: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onReact: (String) -> Unit,
|
||||
onReply: () -> Unit,
|
||||
onEdit: () -> Unit,
|
||||
onStartThread: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = null,
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(0.dp)) {
|
||||
// Quick emoji reactions
|
||||
LazyRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
) {
|
||||
items(QUICK_REACTIONS) { emoji ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.clickable { onReact(emoji) },
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(emoji, fontSize = 20.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
TextButton(
|
||||
onClick = onReply,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Icon(Icons.AutoMirrored.Filled.Reply, null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Reply", modifier = Modifier.weight(1f))
|
||||
}
|
||||
if (isOwnMessage) {
|
||||
TextButton(
|
||||
onClick = onEdit,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Icon(Icons.Default.Check, null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Edit", modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
if (!isInThread) {
|
||||
TextButton(
|
||||
onClick = onStartThread,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Icon(Icons.Default.Tag, null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Start Thread", modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReplyPreviewBar(senderName: String, body: String, onDismiss: () -> Unit) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(3.dp)
|
||||
.height(36.dp)
|
||||
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp)),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
"Replying to $senderName",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
body,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onDismiss, modifier = Modifier.size(32.dp)) {
|
||||
Icon(Icons.Default.Close, "Cancel reply", modifier = Modifier.size(16.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EditModeBar(body: String, onDismiss: () -> Unit) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(3.dp)
|
||||
.height(36.dp)
|
||||
.background(MaterialTheme.colorScheme.tertiary, RoundedCornerShape(2.dp)),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
"Editing message",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
Text(
|
||||
body,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onDismiss, modifier = Modifier.size(32.dp)) {
|
||||
Icon(Icons.Default.Close, "Cancel edit", modifier = Modifier.size(16.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user