android: Live messages without sending an empty text (#1709)

* android: Live messages without sending an empty text

* Better quoted messages handling

* Do not add item into preview

* Change

* Changes
This commit is contained in:
Stanislav Dmitrenko
2023-01-09 18:30:26 +00:00
committed by GitHub
parent cba24983e6
commit 5a7d61c964
6 changed files with 102 additions and 16 deletions
@@ -180,7 +180,11 @@ class ChatModel(val controller: ChatController) {
}
// add to current chat
if (chatId.value == cInfo.id) {
chatItems.add(cItem)
if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
chatItems.add(kotlin.math.max(0, chatItems.lastIndex), cItem)
} else {
chatItems.add(cItem)
}
}
}
@@ -255,6 +259,21 @@ class ChatModel(val controller: ChatController) {
}
}
fun addLiveChatItemDummy(quotedCItem: ChatItem?, chatInfo: ChatInfo): ChatItem {
val quoted = if (quotedCItem?.content?.msgContent != null) {
CIQuote(chatDir = quotedCItem.chatDir, itemId = quotedCItem.id, sentAt = quotedCItem.meta.createdAt, content = quotedCItem.content.msgContent!!)
} else null
val cItem = ChatItem.liveChatItemDummy(chatInfo is ChatInfo.Direct, quoted)
chatItems.add(cItem)
return cItem
}
fun removeLiveChatItemDummy() {
if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
chatItems.removeLast()
}
}
fun markChatItemsRead(cInfo: ChatInfo, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) {
val markedRead = markItemsReadInCurrentChat(cInfo, range)
// update preview
@@ -1278,7 +1297,8 @@ data class ChatItem (
}
private const val TEMP_DELETED_CHAT_ITEM_ID = -1L
const val TEMP_LIVE_CHAT_ITEM_ID = -2L
val deletedItemDummy: ChatItem
get() = ChatItem(
chatDir = CIDirection.DirectRcv(),
@@ -1300,6 +1320,26 @@ data class ChatItem (
file = null
)
fun liveChatItemDummy(direct: Boolean, quoted: CIQuote?): ChatItem = ChatItem(
chatDir = if (direct) CIDirection.DirectSnd() else CIDirection.GroupSnd(),
meta = CIMeta(
itemId = TEMP_LIVE_CHAT_ITEM_ID,
itemTs = Clock.System.now(),
itemText = "",
itemStatus = CIStatus.RcvRead(),
createdAt = Clock.System.now(),
updatedAt = Clock.System.now(),
itemDeleted = false,
itemEdited = false,
itemTimed = null,
itemLive = true,
editable = false
),
content = CIContent.SndMsgContent(MsgContent.MCText("")),
quotedItem = quoted,
file = null
)
fun invalidJSON(json: String): ChatItem =
ChatItem(
chatDir = CIDirection.DirectSnd(),
@@ -147,8 +147,8 @@ fun TerminalLayout(
sendMessage = sendCommand,
sendLiveMessage = null,
updateLiveMessage = null,
::onMessageChange,
textStyle
onMessageChange = ::onMessageChange,
textStyle = textStyle
)
}
},
@@ -568,7 +568,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
scope.launch {
if (composeState.value.editing) {
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
} else {
} else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem))
}
}
@@ -41,6 +41,7 @@ import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.chat.item.*
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
@@ -67,7 +68,8 @@ sealed class ComposeContextItem {
data class LiveMessage(
val chatItem: ChatItem,
val typedMsg: String,
val sentMsg: String
val sentMsg: String,
val sent: Boolean
)
@Serializable
@@ -352,6 +354,7 @@ fun ComposeView(
chosenContent.value = emptyList()
chosenAudio.value = null
chosenFile.value = null
chatModel.removeLiveChatItemDummy()
}
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: String? = null, live: Boolean = false): ChatItem? {
@@ -430,7 +433,7 @@ fun ComposeView(
if (cs.contextItem is ComposeContextItem.EditingItem) {
val ei = cs.contextItem.chatItem
sent = updateMessage(ei, cInfo, live)
} else if (liveMessage != null) {
} else if (liveMessage != null && liveMessage.sent) {
sent = updateMessage(liveMessage.chatItem, cInfo, live)
} else {
val msgs: ArrayList<MsgContent> = ArrayList()
@@ -571,11 +574,14 @@ fun ComposeView(
suspend fun sendLiveMessage() {
val typedMsg = composeState.value.message
val sentMsg = truncateToWords(typedMsg)
if (composeState.value.liveMessage == null) {
if (sentMsg.isNotEmpty() && (composeState.value.liveMessage == null || composeState.value.liveMessage?.sent == false)) {
val ci = sendMessageAsync(sentMsg, live = true)
if (ci != null) {
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg))
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true))
}
} else if (composeState.value.liveMessage == null) {
val cItem = chatModel.addLiveChatItemDummy((composeState.value.contextItem as? ComposeContextItem.QuotedItem)?.chatItem, chat.chatInfo)
composeState.value = composeState.value.copy(liveMessage = LiveMessage(cItem, typedMsg = typedMsg, sentMsg = sentMsg, sent = false))
}
}
@@ -592,7 +598,7 @@ fun ComposeView(
if (sentMsg != null) {
val ci = sendMessageAsync(sentMsg, live = true)
if (ci != null) {
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg))
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true))
}
} else if (liveMessage.typedMsg != typedMsg) {
composeState.value = composeState.value.copy(liveMessage = liveMessage.copy(typedMsg = typedMsg))
@@ -696,14 +702,28 @@ fun ComposeView(
}
}
}
LaunchedEffect(Unit) {
snapshotFlow { composeState.value.contextItem }
.distinctUntilChanged()
.collect {
if (composeState.value.liveMessage?.sent == false) {
chatModel.removeLiveChatItemDummy()
chatModel.addLiveChatItemDummy((it as? ComposeContextItem.QuotedItem)?.chatItem, chat.chatInfo)
}
}
}
val activity = LocalContext.current as Activity
DisposableEffect(Unit) {
val orientation = activity.resources.configuration.orientation
onDispose {
if (orientation == activity.resources.configuration.orientation && composeState.value.liveMessage != null) {
sendMessage()
resetLinkPreview()
if (orientation == activity.resources.configuration.orientation) {
val cs = composeState.value
if (cs.liveMessage != null && (cs.message.isNotEmpty() || cs.liveMessage.sent)) {
sendMessage()
resetLinkPreview()
}
chatModel.removeLiveChatItemDummy()
}
}
}
@@ -723,6 +743,10 @@ fun ComposeView(
},
sendLiveMessage = ::sendLiveMessage,
updateLiveMessage = ::updateLiveMessage,
cancelLiveMessage = {
composeState.value = composeState.value.copy(liveMessage = null)
chatModel.removeLiveChatItemDummy()
},
onMessageChange = ::onMessageChange,
textStyle = textStyle
)
@@ -37,7 +37,6 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat
@@ -65,6 +64,7 @@ fun SendMsgView(
sendMessage: () -> Unit,
sendLiveMessage: ( suspend () -> Unit)? = null,
updateLiveMessage: (suspend () -> Unit)? = null,
cancelLiveMessage: (() -> Unit)? = null,
onMessageChange: (String) -> Unit,
textStyle: MutableState<TextStyle>
) {
@@ -119,9 +119,14 @@ fun SendMsgView(
}
}
}
cs.liveMessage?.sent == false && cs.message.isEmpty() -> {
CancelLiveMessageButton {
cancelLiveMessage?.invoke()
}
}
else -> {
val icon = if (cs.editing || cs.liveMessage != null) Icons.Filled.Check else Icons.Outlined.ArrowUpward
val color = if (cs.sendEnabled()) MaterialTheme.colors.primary else HighOrLowlight
val color = if (cs.sendEnabled() && cs.message.isNotEmpty()) MaterialTheme.colors.primary else HighOrLowlight
if (composeState.value.liveMessage == null &&
cs.preview !is ComposePreview.VoicePreview && !cs.editing &&
sendLiveMessage != null && updateLiveMessage != null
@@ -144,7 +149,7 @@ fun SendMsgView(
)
}
} else {
SendTextButton(icon, color, sendButtonSize, sendButtonAlpha, cs.sendEnabled(), sendMessage)
SendTextButton(icon, color, sendButtonSize, sendButtonAlpha, cs.sendEnabled() && cs.message.isNotEmpty(), sendMessage)
}
}
}
@@ -373,6 +378,22 @@ private fun ProgressIndicator() {
CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = HighOrLowlight, strokeWidth = 3.dp)
}
@Composable
private fun CancelLiveMessageButton(
onClick: () -> Unit
) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
Icons.Filled.Close,
stringResource(R.string.icon_descr_cancel_live_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
)
}
}
@Composable
private fun SendTextButton(
icon: ImageVector,
@@ -271,6 +271,7 @@
<string name="live_message">Live message!</string>
<string name="send_live_message_desc">Send a live message - it will update for the recipient(s) as you type it</string>
<string name="send_verb">Send</string>
<string name="icon_descr_cancel_live_message">Cancel live message</string>
<!-- General Actions / Responses -->
<string name="back">Back</string>