android: refactor compose (#579)
This commit is contained in:
@@ -4,7 +4,6 @@ import android.net.Uri
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.*
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.chat.ComposeState
|
||||
import chat.simplex.app.views.chat.SendMsgView
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import com.google.accompanist.insets.ProvideWindowInsets
|
||||
@@ -25,32 +26,42 @@ import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun TerminalView(chatModel: ChatModel, close: () -> Unit) {
|
||||
val composeState = remember { mutableStateOf(ComposeState()) }
|
||||
BackHandler(onBack = close)
|
||||
TerminalLayout(chatModel.terminalItems, close) { cmd ->
|
||||
withApi {
|
||||
// show "in progress"
|
||||
chatModel.controller.sendCmd(CC.Console(cmd))
|
||||
// hide "in progress"
|
||||
}
|
||||
}
|
||||
TerminalLayout(
|
||||
chatModel.terminalItems,
|
||||
composeState,
|
||||
sendCommand = {
|
||||
withApi {
|
||||
// show "in progress"
|
||||
chatModel.controller.sendCmd(CC.Console(composeState.value.message))
|
||||
// hide "in progress"
|
||||
}
|
||||
},
|
||||
close
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TerminalLayout(terminalItems: List<TerminalItem>, close: () -> Unit, sendCommand: (String) -> Unit) {
|
||||
var msg = remember { mutableStateOf("") }
|
||||
fun TerminalLayout(
|
||||
terminalItems: List<TerminalItem>,
|
||||
composeState: MutableState<ComposeState>,
|
||||
sendCommand: () -> Unit,
|
||||
close: () -> Unit
|
||||
) {
|
||||
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
||||
val textStyle = remember { mutableStateOf(smallFont) }
|
||||
|
||||
fun onMessageChange(s: String) {
|
||||
composeState.value = composeState.value.copy(message = s)
|
||||
}
|
||||
|
||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
||||
Scaffold(
|
||||
topBar = { CloseSheetBar(close) },
|
||||
bottomBar = {
|
||||
Box(Modifier.padding(horizontal = 8.dp)) {
|
||||
SendMsgView(
|
||||
msg = msg,
|
||||
linkPreview = remember { mutableStateOf(null) },
|
||||
cancelledLinks = remember { mutableSetOf() },
|
||||
parseMarkdown = { null },
|
||||
sendMessage = sendCommand,
|
||||
sendEnabled = msg.value.isNotEmpty()
|
||||
)
|
||||
SendMsgView(composeState, sendCommand, ::onMessageChange, textStyle)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.navigationBarsWithImePadding()
|
||||
@@ -108,8 +119,9 @@ fun PreviewTerminalLayout() {
|
||||
SimpleXTheme {
|
||||
TerminalLayout(
|
||||
terminalItems = TerminalItem.sampleData,
|
||||
close = {},
|
||||
sendCommand = {}
|
||||
composeState = remember { mutableStateOf(ComposeState()) },
|
||||
sendCommand = {},
|
||||
close = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package chat.simplex.app.views.chat
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
@@ -38,24 +37,19 @@ import com.google.accompanist.insets.ProvideWindowInsets
|
||||
import com.google.accompanist.insets.navigationBarsWithImePadding
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.datetime.Clock
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
@Composable
|
||||
fun ChatView(chatModel: ChatModel) {
|
||||
val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }
|
||||
val user = chatModel.currentUser.value
|
||||
val composeState = remember { mutableStateOf(ComposeState()) }
|
||||
val attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
||||
val scope = rememberCoroutineScope()
|
||||
val chosenImage = remember { mutableStateOf<Bitmap?>(null) }
|
||||
|
||||
if (chat == null || user == null) {
|
||||
chatModel.chatId.value = null
|
||||
} else {
|
||||
val context = LocalContext.current
|
||||
val quotedItem = remember { mutableStateOf<ChatItem?>(null) }
|
||||
val editingItem = remember { mutableStateOf<ChatItem?>(null) }
|
||||
val linkPreview = remember { mutableStateOf<LinkPreview?>(null) }
|
||||
val chosenImage = remember { mutableStateOf<Bitmap?>(null) }
|
||||
val imagePreview = remember { mutableStateOf<String?>(null) }
|
||||
var msg = remember { mutableStateOf("") }
|
||||
|
||||
BackHandler { chatModel.chatId.value = null }
|
||||
// TODO a more advanced version would mark as read only if in view
|
||||
LaunchedEffect(chat.chatItems) {
|
||||
@@ -73,7 +67,22 @@ fun ChatView(chatModel: ChatModel) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ChatLayout(user, chat, chatModel.chatItems, msg, quotedItem, editingItem, linkPreview, chosenImage, imagePreview,
|
||||
ChatLayout(
|
||||
user,
|
||||
chat,
|
||||
composeState,
|
||||
composeView = {
|
||||
ComposeView(
|
||||
chatModel,
|
||||
chat,
|
||||
composeState,
|
||||
chosenImage,
|
||||
showAttachmentBottomSheet = { scope.launch { attachmentBottomSheetState.show() } })
|
||||
},
|
||||
chosenImage,
|
||||
scope,
|
||||
attachmentBottomSheetState,
|
||||
chatModel.chatItems,
|
||||
back = { chatModel.chatId.value = null },
|
||||
info = { ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, close) } },
|
||||
openDirectChat = { contactId ->
|
||||
@@ -82,57 +91,6 @@ fun ChatView(chatModel: ChatModel) {
|
||||
}
|
||||
if (c != null) withApi { openChat(chatModel, c.chatInfo) }
|
||||
},
|
||||
sendMessage = { msg ->
|
||||
withApi {
|
||||
// show "in progress"
|
||||
val cInfo = chat.chatInfo
|
||||
val ei = editingItem.value
|
||||
if (ei != null) {
|
||||
val oldMsgContent = ei.content.msgContent
|
||||
if (oldMsgContent != null) {
|
||||
val updatedItem = chatModel.controller.apiUpdateChatItem(
|
||||
type = cInfo.chatType,
|
||||
id = cInfo.apiId,
|
||||
itemId = ei.meta.itemId,
|
||||
mc = updateMsgContent(oldMsgContent, msg)
|
||||
)
|
||||
if (updatedItem != null) chatModel.upsertChatItem(cInfo, updatedItem.chatItem)
|
||||
}
|
||||
} else {
|
||||
var file: String? = null
|
||||
val imagePreviewData = imagePreview.value
|
||||
val chosenImageData = chosenImage.value
|
||||
val linkPreviewData = linkPreview.value
|
||||
val mc = when {
|
||||
imagePreviewData != null && chosenImageData != null -> {
|
||||
file = saveImage(context, chosenImageData)
|
||||
MsgContent.MCImage(msg, imagePreviewData)
|
||||
}
|
||||
linkPreviewData != null -> {
|
||||
MsgContent.MCLink(msg, linkPreviewData)
|
||||
}
|
||||
else -> {
|
||||
MsgContent.MCText(msg)
|
||||
}
|
||||
}
|
||||
val newItem = chatModel.controller.apiSendMessage(
|
||||
type = cInfo.chatType,
|
||||
id = cInfo.apiId,
|
||||
file = file,
|
||||
quotedItemId = quotedItem.value?.meta?.itemId,
|
||||
mc = mc
|
||||
)
|
||||
if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem)
|
||||
}
|
||||
// hide "in progress"
|
||||
editingItem.value = null
|
||||
quotedItem.value = null
|
||||
linkPreview.value = null
|
||||
chosenImage.value = null
|
||||
imagePreview.value = null
|
||||
}
|
||||
},
|
||||
resetMessage = { msg.value = "" },
|
||||
deleteMessage = { itemId, mode ->
|
||||
withApi {
|
||||
val cInfo = chat.chatInfo
|
||||
@@ -144,55 +102,30 @@ fun ChatView(chatModel: ChatModel) {
|
||||
)
|
||||
if (toItem != null) chatModel.removeChatItem(cInfo, toItem.chatItem)
|
||||
}
|
||||
},
|
||||
parseMarkdown = { text -> runBlocking { chatModel.controller.apiParseMarkdown(text) } },
|
||||
onImageChange = { bitmap -> imagePreview.value = resizeImageToStrSize(bitmap, maxDataSize = 14000) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMsgContent(msgContent: MsgContent, text: String): MsgContent {
|
||||
return when (msgContent) {
|
||||
is MsgContent.MCText -> MsgContent.MCText(text)
|
||||
is MsgContent.MCLink -> MsgContent.MCLink(text, preview = msgContent.preview)
|
||||
is MsgContent.MCImage -> MsgContent.MCImage(text, image = msgContent.image)
|
||||
is MsgContent.MCUnknown -> MsgContent.MCUnknown(type = msgContent.type, text = text, json = msgContent.json)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveImage(context: Context, image: Bitmap): String {
|
||||
val dataResized = resizeImageToDataSize(image, maxDataSize = MAX_IMAGE_SIZE)
|
||||
val fileToSave = "image_${System.currentTimeMillis()}.jpg"
|
||||
val file = File(getAppFilesDirectory(context) + "/" + fileToSave)
|
||||
val output = FileOutputStream(file)
|
||||
dataResized.writeTo(output)
|
||||
output.flush()
|
||||
output.close()
|
||||
return fileToSave
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatLayout(
|
||||
user: User,
|
||||
chat: Chat,
|
||||
chatItems: List<ChatItem>,
|
||||
msg: MutableState<String>,
|
||||
quotedItem: MutableState<ChatItem?>,
|
||||
editingItem: MutableState<ChatItem?>,
|
||||
linkPreview: MutableState<LinkPreview?>,
|
||||
composeState: MutableState<ComposeState>,
|
||||
composeView: (@Composable () -> Unit),
|
||||
chosenImage: MutableState<Bitmap?>,
|
||||
imagePreview: MutableState<String?>,
|
||||
scope: CoroutineScope,
|
||||
attachmentBottomSheetState: ModalBottomSheetState,
|
||||
chatItems: List<ChatItem>,
|
||||
back: () -> Unit,
|
||||
info: () -> Unit,
|
||||
openDirectChat: (Long) -> Unit,
|
||||
sendMessage: (String) -> Unit,
|
||||
resetMessage: () -> Unit,
|
||||
deleteMessage: (Long, CIDeleteMode) -> Unit,
|
||||
parseMarkdown: (String) -> List<FormattedText>?,
|
||||
onImageChange: (Bitmap) -> Unit
|
||||
deleteMessage: (Long, CIDeleteMode) -> Unit
|
||||
) {
|
||||
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
||||
val scope = rememberCoroutineScope()
|
||||
fun onImageChange(bitmap: Bitmap) {
|
||||
val imagePreview = resizeImageToStrSize(bitmap, maxDataSize = 14000)
|
||||
composeState.value = composeState.value.copy(preview = ComposePreview.ImagePreview(imagePreview))
|
||||
}
|
||||
|
||||
Surface(
|
||||
Modifier
|
||||
@@ -206,26 +139,21 @@ fun ChatLayout(
|
||||
sheetContent = {
|
||||
GetImageBottomSheet(
|
||||
chosenImage,
|
||||
onImageChange = onImageChange,
|
||||
::onImageChange,
|
||||
hideBottomSheet = {
|
||||
scope.launch { bottomSheetModalState.hide() }
|
||||
scope.launch { attachmentBottomSheetState.hide() }
|
||||
})
|
||||
},
|
||||
sheetState = bottomSheetModalState,
|
||||
sheetState = attachmentBottomSheetState,
|
||||
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { ChatInfoToolbar(chat, back, info) },
|
||||
bottomBar = {
|
||||
ComposeView(
|
||||
msg, quotedItem, editingItem, linkPreview, chosenImage, imagePreview, sendMessage, resetMessage, parseMarkdown,
|
||||
showBottomSheet = { scope.launch { bottomSheetModalState.show() } }
|
||||
)
|
||||
},
|
||||
bottomBar = composeView,
|
||||
modifier = Modifier.navigationBarsWithImePadding()
|
||||
) { contentPadding ->
|
||||
Box(Modifier.padding(contentPadding)) {
|
||||
ChatItemsList(user, chat, chatItems, msg, quotedItem, editingItem, openDirectChat, deleteMessage)
|
||||
ChatItemsList(user, chat, composeState, chatItems, openDirectChat, deleteMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,10 +227,8 @@ val CIListStateSaver = run {
|
||||
fun ChatItemsList(
|
||||
user: User,
|
||||
chat: Chat,
|
||||
composeState: MutableState<ComposeState>,
|
||||
chatItems: List<ChatItem>,
|
||||
msg: MutableState<String>,
|
||||
quotedItem: MutableState<ChatItem?>,
|
||||
editingItem: MutableState<ChatItem?>,
|
||||
openDirectChat: (Long) -> Unit,
|
||||
deleteMessage: (Long, CIDeleteMode) -> Unit
|
||||
) {
|
||||
@@ -342,11 +268,11 @@ fun ChatItemsList(
|
||||
} else {
|
||||
Spacer(Modifier.size(42.dp))
|
||||
}
|
||||
ChatItemView(user, cItem, msg, quotedItem, editingItem, cxt, uriHandler, showMember = showMember, deleteMessage = deleteMessage)
|
||||
ChatItemView(user, cItem, composeState, cxt, uriHandler, showMember = showMember, deleteMessage = deleteMessage)
|
||||
}
|
||||
} else {
|
||||
Box(Modifier.padding(start = 86.dp, end = 12.dp)) {
|
||||
ChatItemView(user, cItem, msg, quotedItem, editingItem, cxt, uriHandler, deleteMessage = deleteMessage)
|
||||
ChatItemView(user, cItem, composeState, cxt, uriHandler, deleteMessage = deleteMessage)
|
||||
}
|
||||
}
|
||||
} else { // direct message
|
||||
@@ -357,7 +283,7 @@ fun ChatItemsList(
|
||||
end = if (sent) 12.dp else 76.dp,
|
||||
)
|
||||
) {
|
||||
ChatItemView(user, cItem, msg, quotedItem, editingItem, cxt, uriHandler, deleteMessage = deleteMessage)
|
||||
ChatItemView(user, cItem, composeState, cxt, uriHandler, deleteMessage = deleteMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,21 +341,16 @@ fun PreviewChatLayout() {
|
||||
chatItems = chatItems,
|
||||
chatStats = Chat.ChatStats()
|
||||
),
|
||||
chatItems = chatItems,
|
||||
msg = remember { mutableStateOf("") },
|
||||
quotedItem = remember { mutableStateOf(null) },
|
||||
editingItem = remember { mutableStateOf(null) },
|
||||
linkPreview = remember { mutableStateOf(null) },
|
||||
composeState = remember { mutableStateOf(ComposeState()) },
|
||||
composeView = {},
|
||||
chosenImage = remember { mutableStateOf(null) },
|
||||
imagePreview = remember { mutableStateOf(null) },
|
||||
scope = rememberCoroutineScope(),
|
||||
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
|
||||
chatItems = chatItems,
|
||||
back = {},
|
||||
info = {},
|
||||
openDirectChat = {},
|
||||
sendMessage = {},
|
||||
resetMessage = {},
|
||||
deleteMessage = { _, _ -> },
|
||||
parseMarkdown = { null },
|
||||
onImageChange = {}
|
||||
deleteMessage = { _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -463,21 +384,16 @@ fun PreviewGroupChatLayout() {
|
||||
chatItems = chatItems,
|
||||
chatStats = Chat.ChatStats()
|
||||
),
|
||||
chatItems = chatItems,
|
||||
msg = remember { mutableStateOf("") },
|
||||
quotedItem = remember { mutableStateOf(null) },
|
||||
editingItem = remember { mutableStateOf(null) },
|
||||
linkPreview = remember { mutableStateOf(null) },
|
||||
composeState = remember { mutableStateOf(ComposeState()) },
|
||||
composeView = {},
|
||||
chosenImage = remember { mutableStateOf(null) },
|
||||
imagePreview = remember { mutableStateOf(null) },
|
||||
scope = rememberCoroutineScope(),
|
||||
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
|
||||
chatItems = chatItems,
|
||||
back = {},
|
||||
info = {},
|
||||
openDirectChat = {},
|
||||
sendMessage = {},
|
||||
resetMessage = {},
|
||||
deleteMessage = { _, _ -> },
|
||||
parseMarkdown = { null },
|
||||
onImageChange = {}
|
||||
deleteMessage = { _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import chat.simplex.app.views.chat.item.SentColorLight
|
||||
import chat.simplex.app.views.helpers.base64ToBitmap
|
||||
|
||||
@Composable
|
||||
fun ComposeImageView(image: String, cancelImage: () -> Unit) {
|
||||
fun ComposeImageView(image: String, cancelImage: () -> Unit, cancelEnabled: Boolean) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -33,13 +33,15 @@ fun ComposeImageView(image: String, cancelImage: () -> Unit) {
|
||||
.padding(end = 8.dp)
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
IconButton(onClick = cancelImage, modifier = Modifier.padding(0.dp)) {
|
||||
Icon(
|
||||
Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.icon_descr_cancel_image_preview),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.padding(10.dp)
|
||||
)
|
||||
if (cancelEnabled) {
|
||||
IconButton(onClick = cancelImage, modifier = Modifier.padding(0.dp)) {
|
||||
Icon(
|
||||
Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.icon_descr_cancel_image_preview),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.padding(10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package chat.simplex.app.views.chat
|
||||
|
||||
import ComposeImageView
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AttachFile
|
||||
import androidx.compose.runtime.*
|
||||
@@ -14,81 +14,312 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.views.helpers.ComposeLinkView
|
||||
import chat.simplex.app.views.chat.item.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
sealed class ComposePreview {
|
||||
object NoPreview: ComposePreview()
|
||||
class CLinkPreview(val linkPreview: LinkPreview): ComposePreview()
|
||||
class ImagePreview(val image: String): ComposePreview()
|
||||
}
|
||||
|
||||
sealed class ComposeContextItem {
|
||||
object NoContextItem: ComposeContextItem()
|
||||
class QuotedItem(val chatItem: ChatItem): ComposeContextItem()
|
||||
class EditingItem(val chatItem: ChatItem): ComposeContextItem()
|
||||
}
|
||||
|
||||
data class ComposeState(
|
||||
val message: String = "",
|
||||
val preview: ComposePreview = ComposePreview.NoPreview,
|
||||
val contextItem: ComposeContextItem = ComposeContextItem.NoContextItem,
|
||||
val inProgress: Boolean = false
|
||||
) {
|
||||
constructor(editingItem: ChatItem): this(
|
||||
editingItem.content.text,
|
||||
chatItemPreview(editingItem),
|
||||
ComposeContextItem.EditingItem(editingItem)
|
||||
)
|
||||
|
||||
val editing: Boolean
|
||||
get() =
|
||||
when (contextItem) {
|
||||
is ComposeContextItem.EditingItem -> true
|
||||
else -> false
|
||||
}
|
||||
val sendEnabled: Boolean
|
||||
get() =
|
||||
when (preview) {
|
||||
is ComposePreview.ImagePreview -> true
|
||||
else -> message.isNotEmpty()
|
||||
}
|
||||
val linkPreviewAllowed: Boolean
|
||||
get() =
|
||||
when (preview) {
|
||||
is ComposePreview.ImagePreview -> false
|
||||
else -> true
|
||||
}
|
||||
val linkPreview: LinkPreview?
|
||||
get() =
|
||||
when (preview) {
|
||||
is ComposePreview.CLinkPreview -> preview.linkPreview
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun chatItemPreview(chatItem: ChatItem): ComposePreview {
|
||||
return when (val mc = chatItem.content.msgContent) {
|
||||
is MsgContent.MCLink -> ComposePreview.CLinkPreview(linkPreview = mc.preview)
|
||||
is MsgContent.MCImage -> ComposePreview.ImagePreview(image = mc.image)
|
||||
else -> ComposePreview.NoPreview
|
||||
}
|
||||
}
|
||||
|
||||
// TODO ComposeState
|
||||
@Composable
|
||||
fun ComposeView(
|
||||
msg: MutableState<String>,
|
||||
quotedItem: MutableState<ChatItem?>,
|
||||
editingItem: MutableState<ChatItem?>,
|
||||
linkPreview: MutableState<LinkPreview?>,
|
||||
chatModel: ChatModel,
|
||||
chat: Chat,
|
||||
composeState: MutableState<ComposeState>,
|
||||
chosenImage: MutableState<Bitmap?>,
|
||||
imagePreview: MutableState<String?>,
|
||||
sendMessage: (String) -> Unit,
|
||||
resetMessage: () -> Unit,
|
||||
parseMarkdown: (String) -> List<FormattedText>?,
|
||||
showBottomSheet: () -> Unit
|
||||
showAttachmentBottomSheet: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val linkUrl = remember { mutableStateOf<String?>(null) }
|
||||
val prevLinkUrl = remember { mutableStateOf<String?>(null) }
|
||||
val pendingLinkUrl = remember { mutableStateOf<String?>(null) }
|
||||
val cancelledLinks = remember { mutableSetOf<String>() }
|
||||
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
||||
val textStyle = remember { mutableStateOf(smallFont) }
|
||||
|
||||
fun cancelPreview() {
|
||||
val uri = linkPreview.value?.uri
|
||||
fun isSimplexLink(link: String): Boolean =
|
||||
link.startsWith("https://simplex.chat", true) || link.startsWith("http://simplex.chat", true)
|
||||
|
||||
fun parseMessage(msg: String): String? {
|
||||
val parsedMsg = runBlocking { chatModel.controller.apiParseMarkdown(msg) }
|
||||
val link = parsedMsg?.firstOrNull { ft -> ft.format is Format.Uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) }
|
||||
return link?.text
|
||||
}
|
||||
|
||||
fun loadLinkPreview(url: String, wait: Long? = null) {
|
||||
if (pendingLinkUrl.value == url) {
|
||||
withApi {
|
||||
if (wait != null) delay(wait)
|
||||
val lp = getLinkPreview(url)
|
||||
if (lp != null && pendingLinkUrl.value == url) {
|
||||
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(lp))
|
||||
pendingLinkUrl.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showLinkPreview(s: String) {
|
||||
prevLinkUrl.value = linkUrl.value
|
||||
linkUrl.value = parseMessage(s)
|
||||
val url = linkUrl.value
|
||||
if (url != null) {
|
||||
if (url != composeState.value.linkPreview?.uri && url != pendingLinkUrl.value) {
|
||||
pendingLinkUrl.value = url
|
||||
loadLinkPreview(url, wait = if (prevLinkUrl.value == url) null else 1500L)
|
||||
}
|
||||
} else {
|
||||
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetLinkPreview() {
|
||||
linkUrl.value = null
|
||||
prevLinkUrl.value = null
|
||||
pendingLinkUrl.value = null
|
||||
cancelledLinks.clear()
|
||||
}
|
||||
|
||||
fun checkLinkPreview(): MsgContent {
|
||||
val cs = composeState.value
|
||||
return when (val composePreview = cs.preview) {
|
||||
is ComposePreview.CLinkPreview -> {
|
||||
val url = parseMessage(cs.message)
|
||||
val lp = composePreview.linkPreview
|
||||
if (url == lp.uri) {
|
||||
MsgContent.MCLink(cs.message, preview = lp)
|
||||
} else {
|
||||
MsgContent.MCText(cs.message)
|
||||
}
|
||||
}
|
||||
else -> MsgContent.MCText(cs.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMsgContent(msgContent: MsgContent): MsgContent {
|
||||
val cs = composeState.value
|
||||
return when (msgContent) {
|
||||
is MsgContent.MCText -> checkLinkPreview()
|
||||
is MsgContent.MCLink -> checkLinkPreview()
|
||||
is MsgContent.MCImage -> MsgContent.MCImage(cs.message, image = msgContent.image)
|
||||
is MsgContent.MCUnknown -> MsgContent.MCUnknown(type = msgContent.type, text = cs.message, json = msgContent.json)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveImage(context: Context, image: Bitmap): String? {
|
||||
return try {
|
||||
val dataResized = resizeImageToDataSize(image, maxDataSize = MAX_IMAGE_SIZE)
|
||||
val fileToSave = "image_${System.currentTimeMillis()}.jpg"
|
||||
val file = File(getAppFilesDirectory(context) + "/" + fileToSave)
|
||||
val output = FileOutputStream(file)
|
||||
dataResized.writeTo(output)
|
||||
output.flush()
|
||||
output.close()
|
||||
fileToSave
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage() {
|
||||
withApi {
|
||||
// show "in progress"
|
||||
val cInfo = chat.chatInfo
|
||||
val cs = composeState.value
|
||||
when (val contextItem = cs.contextItem) {
|
||||
is ComposeContextItem.EditingItem -> {
|
||||
val ei = contextItem.chatItem
|
||||
val oldMsgContent = ei.content.msgContent
|
||||
if (oldMsgContent != null) {
|
||||
val updatedItem = chatModel.controller.apiUpdateChatItem(
|
||||
type = cInfo.chatType,
|
||||
id = cInfo.apiId,
|
||||
itemId = ei.meta.itemId,
|
||||
mc = updateMsgContent(oldMsgContent)
|
||||
)
|
||||
if (updatedItem != null) chatModel.upsertChatItem(cInfo, updatedItem.chatItem)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
var mc: MsgContent? = null
|
||||
var file: String? = null
|
||||
when (val preview = cs.preview) {
|
||||
ComposePreview.NoPreview -> mc = MsgContent.MCText(cs.message)
|
||||
is ComposePreview.CLinkPreview -> mc = checkLinkPreview()
|
||||
is ComposePreview.ImagePreview -> {
|
||||
val chosenImageVal = chosenImage.value
|
||||
if (chosenImageVal != null) {
|
||||
file = saveImage(context, chosenImageVal)
|
||||
if (file != null) {
|
||||
mc = MsgContent.MCImage(cs.message, preview.image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val quotedItemId: Long? = when (contextItem) {
|
||||
is ComposeContextItem.QuotedItem -> contextItem.chatItem.id
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (mc != null) {
|
||||
val aChatItem = chatModel.controller.apiSendMessage(
|
||||
type = cInfo.chatType,
|
||||
id = cInfo.apiId,
|
||||
file = file,
|
||||
quotedItemId = quotedItemId,
|
||||
mc = mc
|
||||
)
|
||||
if (aChatItem != null) chatModel.addChatItem(cInfo, aChatItem.chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
// hide "in progress"
|
||||
composeState.value = ComposeState()
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessageChange(s: String) {
|
||||
composeState.value = composeState.value.copy(message = s)
|
||||
if (isShortEmoji(s)) {
|
||||
textStyle.value = if (s.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont
|
||||
} else {
|
||||
textStyle.value = smallFont
|
||||
if (composeState.value.linkPreviewAllowed) {
|
||||
if (s.isNotEmpty()) showLinkPreview(s)
|
||||
else resetLinkPreview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelLinkPreview() {
|
||||
val uri = composeState.value.linkPreview?.uri
|
||||
if (uri != null) {
|
||||
cancelledLinks.add(uri)
|
||||
}
|
||||
linkPreview.value = null
|
||||
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
|
||||
}
|
||||
|
||||
fun cancelImage() {
|
||||
chosenImage.value = null
|
||||
imagePreview.value = null
|
||||
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun previewView() {
|
||||
when (val preview = composeState.value.preview) {
|
||||
ComposePreview.NoPreview -> {}
|
||||
is ComposePreview.CLinkPreview -> ComposeLinkView(preview.linkPreview, ::cancelLinkPreview)
|
||||
is ComposePreview.ImagePreview -> ComposeImageView(
|
||||
preview.image,
|
||||
::cancelImage,
|
||||
cancelEnabled = !composeState.value.editing
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun contextItemView() {
|
||||
when (val contextItem = composeState.value.contextItem) {
|
||||
ComposeContextItem.NoContextItem -> {}
|
||||
is ComposeContextItem.QuotedItem -> ContextItemView(contextItem.chatItem) { composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem) }
|
||||
is ComposeContextItem.EditingItem -> ContextItemView(contextItem.chatItem) { composeState.value = ComposeState() }
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
val ip = imagePreview.value
|
||||
if (ip != null) {
|
||||
ComposeImageView(ip, ::cancelImage)
|
||||
} else {
|
||||
val lp = linkPreview.value
|
||||
if (lp != null) ComposeLinkView(lp, ::cancelPreview)
|
||||
}
|
||||
when {
|
||||
quotedItem.value != null -> {
|
||||
ContextItemView(quotedItem)
|
||||
}
|
||||
editingItem.value != null -> {
|
||||
ContextItemView(editingItem, editing = editingItem.value != null, resetMessage)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
contextItemView()
|
||||
previewView()
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 4.dp, end = 8.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
val attachEnabled = !composeState.value.editing
|
||||
Box(Modifier.padding(bottom = 12.dp)) {
|
||||
Icon(
|
||||
Icons.Filled.AttachFile,
|
||||
contentDescription = stringResource(R.string.attach),
|
||||
tint = if (editingItem.value == null) MaterialTheme.colors.primary else Color.Gray,
|
||||
tint = if (attachEnabled) MaterialTheme.colors.primary else Color.Gray,
|
||||
modifier = Modifier
|
||||
.size(28.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable {
|
||||
if (editingItem.value == null) {
|
||||
showBottomSheet()
|
||||
if (attachEnabled) {
|
||||
showAttachmentBottomSheet()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
SendMsgView(
|
||||
msg, linkPreview, cancelledLinks, parseMarkdown, sendMessage,
|
||||
editing = editingItem.value != null, sendEnabled = msg.value.isNotEmpty() || imagePreview.value != null
|
||||
composeState,
|
||||
sendMessage = {
|
||||
sendMessage()
|
||||
resetLinkPreview()
|
||||
},
|
||||
::onMessageChange,
|
||||
textStyle
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,41 +22,32 @@ import kotlinx.datetime.Clock
|
||||
|
||||
@Composable
|
||||
fun ContextItemView(
|
||||
contextItem: MutableState<ChatItem?>,
|
||||
editing: Boolean = false,
|
||||
resetMessage: () -> Unit = {}
|
||||
contextItem: ChatItem,
|
||||
cancelContextItem: () -> Unit
|
||||
) {
|
||||
val cxtItem = contextItem.value
|
||||
if (cxtItem != null) {
|
||||
val sent = cxtItem.chatDir.sent
|
||||
Row(
|
||||
val sent = contextItem.chatDir.sent
|
||||
Row(
|
||||
Modifier
|
||||
.padding(top = 8.dp)
|
||||
.background(if (sent) SentColorLight else ReceivedColorLight),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(top = 8.dp)
|
||||
.background(if (sent) SentColorLight else ReceivedColorLight),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
.padding(start = 16.dp)
|
||||
.padding(vertical = 12.dp)
|
||||
.fillMaxWidth()
|
||||
.weight(1F)
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.padding(vertical = 12.dp)
|
||||
.fillMaxWidth()
|
||||
.weight(1F)
|
||||
) {
|
||||
ContextItemText(cxtItem)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
contextItem.value = null
|
||||
if (editing) {
|
||||
resetMessage()
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.cancel_verb),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.padding(10.dp)
|
||||
)
|
||||
}
|
||||
ContextItemText(contextItem)
|
||||
}
|
||||
IconButton(onClick = cancelContextItem) {
|
||||
Icon(
|
||||
Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.cancel_verb),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.padding(10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,13 +71,8 @@ private fun ContextItemText(cxtItem: ChatItem) {
|
||||
fun PreviewContextItemView() {
|
||||
SimpleXTheme {
|
||||
ContextItemView(
|
||||
contextItem = remember {
|
||||
mutableStateOf(
|
||||
ChatItem.getSampleData(
|
||||
1, CIDirection.DirectRcv(), Clock.System.now(), "hello"
|
||||
)
|
||||
)
|
||||
}
|
||||
contextItem = ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), "hello"),
|
||||
cancelContextItem = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -25,83 +26,19 @@ import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.chat.item.*
|
||||
import chat.simplex.app.views.helpers.getLinkPreview
|
||||
import chat.simplex.app.views.helpers.withApi
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
fun SendMsgView(
|
||||
msg: MutableState<String>,
|
||||
linkPreview: MutableState<LinkPreview?>,
|
||||
cancelledLinks: MutableSet<String>,
|
||||
parseMarkdown: (String) -> List<FormattedText>?,
|
||||
sendMessage: (String) -> Unit,
|
||||
editing: Boolean = false,
|
||||
sendEnabled: Boolean = false
|
||||
composeState: MutableState<ComposeState>,
|
||||
sendMessage: () -> Unit,
|
||||
onMessageChange: (String) -> Unit,
|
||||
textStyle: MutableState<TextStyle>
|
||||
) {
|
||||
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
||||
var textStyle by remember { mutableStateOf(smallFont) }
|
||||
val linkUrl = remember { mutableStateOf<String?>(null) }
|
||||
val prevLinkUrl = remember { mutableStateOf<String?>(null) }
|
||||
val pendingLinkUrl = remember { mutableStateOf<String?>(null) }
|
||||
|
||||
fun isSimplexLink(link: String): Boolean =
|
||||
link.startsWith("https://simplex.chat",true) || link.startsWith("http://simplex.chat", true)
|
||||
|
||||
fun parseMessage(msg: String): String? {
|
||||
val parsedMsg = parseMarkdown(msg)
|
||||
val link = parsedMsg?.firstOrNull { ft -> ft.format is Format.Uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) }
|
||||
return link?.text
|
||||
}
|
||||
|
||||
fun loadLinkPreview(url: String, wait: Long? = null) {
|
||||
if (pendingLinkUrl.value == url) {
|
||||
withApi {
|
||||
if (wait != null) delay(wait)
|
||||
val lp = getLinkPreview(url)
|
||||
if (pendingLinkUrl.value == url) {
|
||||
linkPreview.value = lp
|
||||
pendingLinkUrl.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showLinkPreview(s: String) {
|
||||
prevLinkUrl.value = linkUrl.value
|
||||
linkUrl.value = parseMessage(s)
|
||||
val url = linkUrl.value
|
||||
if (url != null) {
|
||||
if (url != linkPreview.value?.uri && url != pendingLinkUrl.value) {
|
||||
pendingLinkUrl.value = url
|
||||
loadLinkPreview(url, wait = if (prevLinkUrl.value == url) null else 1500L)
|
||||
}
|
||||
} else {
|
||||
linkPreview.value = null
|
||||
}
|
||||
}
|
||||
|
||||
fun resetLinkPreview() {
|
||||
linkUrl.value = null
|
||||
prevLinkUrl.value = null
|
||||
pendingLinkUrl.value = null
|
||||
cancelledLinks.clear()
|
||||
}
|
||||
|
||||
val cs = composeState.value
|
||||
BasicTextField(
|
||||
value = msg.value,
|
||||
onValueChange = { s ->
|
||||
msg.value = s
|
||||
if (isShortEmoji(s)) {
|
||||
textStyle = if (s.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont
|
||||
} else {
|
||||
textStyle = smallFont
|
||||
if (s.isNotEmpty()) showLinkPreview(s)
|
||||
else resetLinkPreview()
|
||||
}
|
||||
},
|
||||
textStyle = textStyle,
|
||||
value = cs.message,
|
||||
onValueChange = onMessageChange,
|
||||
textStyle = textStyle.value,
|
||||
maxLines = 16,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
@@ -127,9 +64,10 @@ fun SendMsgView(
|
||||
) {
|
||||
innerTextField()
|
||||
}
|
||||
val color = if (sendEnabled) MaterialTheme.colors.primary else Color.Gray
|
||||
val icon = if (cs.editing) Icons.Filled.Check else Icons.Outlined.ArrowUpward
|
||||
val color = if (cs.sendEnabled) MaterialTheme.colors.primary else Color.Gray
|
||||
Icon(
|
||||
if (editing) Icons.Filled.Check else Icons.Outlined.ArrowUpward,
|
||||
icon,
|
||||
stringResource(R.string.icon_descr_send_message),
|
||||
tint = Color.White,
|
||||
modifier = Modifier
|
||||
@@ -138,11 +76,8 @@ fun SendMsgView(
|
||||
.clip(CircleShape)
|
||||
.background(color)
|
||||
.clickable {
|
||||
if (sendEnabled) {
|
||||
sendMessage(msg.value)
|
||||
msg.value = ""
|
||||
textStyle = smallFont
|
||||
cancelledLinks.clear()
|
||||
if (cs.sendEnabled) {
|
||||
sendMessage()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -160,14 +95,14 @@ fun SendMsgView(
|
||||
)
|
||||
@Composable
|
||||
fun PreviewSendMsgView() {
|
||||
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
||||
val textStyle = remember { mutableStateOf(smallFont) }
|
||||
SimpleXTheme {
|
||||
SendMsgView(
|
||||
msg = remember { mutableStateOf("") },
|
||||
linkPreview = remember {mutableStateOf<LinkPreview?>(null) },
|
||||
cancelledLinks = mutableSetOf(),
|
||||
parseMarkdown = { null },
|
||||
sendMessage = { msg -> println(msg) },
|
||||
sendEnabled = true
|
||||
composeState = remember { mutableStateOf(ComposeState()) },
|
||||
sendMessage = {},
|
||||
onMessageChange = { _ -> },
|
||||
textStyle = textStyle
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -180,15 +115,15 @@ fun PreviewSendMsgView() {
|
||||
)
|
||||
@Composable
|
||||
fun PreviewSendMsgViewEditing() {
|
||||
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
||||
val textStyle = remember { mutableStateOf(smallFont) }
|
||||
val composeStateEditing = ComposeState(editingItem = ChatItem.getSampleData())
|
||||
SimpleXTheme {
|
||||
SendMsgView(
|
||||
msg = remember { mutableStateOf("") },
|
||||
linkPreview = remember {mutableStateOf<LinkPreview?>(null) },
|
||||
cancelledLinks = mutableSetOf(),
|
||||
sendMessage = { msg -> println(msg) },
|
||||
parseMarkdown = { null },
|
||||
editing = true,
|
||||
sendEnabled = true
|
||||
composeState = remember { mutableStateOf(composeStateEditing) },
|
||||
sendMessage = {},
|
||||
onMessageChange = { _ -> },
|
||||
textStyle = textStyle
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.chat.ComposeContextItem
|
||||
import chat.simplex.app.views.chat.ComposeState
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@@ -27,9 +29,7 @@ import kotlinx.datetime.Clock
|
||||
fun ChatItemView(
|
||||
user: User,
|
||||
cItem: ChatItem,
|
||||
msg: MutableState<String>,
|
||||
quotedItem: MutableState<ChatItem?>,
|
||||
editingItem: MutableState<ChatItem?>,
|
||||
composeState: MutableState<ComposeState>,
|
||||
cxt: Context,
|
||||
uriHandler: UriHandler? = null,
|
||||
showMember: Boolean = false,
|
||||
@@ -61,8 +61,7 @@ fun ChatItemView(
|
||||
Modifier.width(220.dp)
|
||||
) {
|
||||
ItemAction(stringResource(R.string.reply_verb), Icons.Outlined.Reply, onClick = {
|
||||
editingItem.value = null
|
||||
quotedItem.value = cItem
|
||||
composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem))
|
||||
showMenu.value = false
|
||||
})
|
||||
ItemAction(stringResource(R.string.share_verb), Icons.Outlined.Share, onClick = {
|
||||
@@ -75,9 +74,7 @@ fun ChatItemView(
|
||||
})
|
||||
if (cItem.chatDir.sent && cItem.meta.editable) {
|
||||
ItemAction(stringResource(R.string.edit_verb), Icons.Filled.Edit, onClick = {
|
||||
quotedItem.value = null
|
||||
editingItem.value = cItem
|
||||
msg.value = cItem.content.text
|
||||
composeState.value = ComposeState(editingItem = cItem)
|
||||
showMenu.value = false
|
||||
})
|
||||
}
|
||||
@@ -148,9 +145,7 @@ fun PreviewChatItemView() {
|
||||
ChatItem.getSampleData(
|
||||
1, CIDirection.DirectSnd(), Clock.System.now(), "hello"
|
||||
),
|
||||
msg = remember { mutableStateOf("") },
|
||||
quotedItem = remember { mutableStateOf(null) },
|
||||
editingItem = remember { mutableStateOf(null) },
|
||||
composeState = remember { mutableStateOf(ComposeState()) },
|
||||
cxt = LocalContext.current,
|
||||
deleteMessage = { _, _ -> }
|
||||
)
|
||||
@@ -164,9 +159,7 @@ fun PreviewChatItemViewDeletedContent() {
|
||||
ChatItemView(
|
||||
User.sampleData,
|
||||
ChatItem.getDeletedContentSampleData(),
|
||||
msg = remember { mutableStateOf("") },
|
||||
quotedItem = remember { mutableStateOf(null) },
|
||||
editingItem = remember { mutableStateOf(null) },
|
||||
composeState = remember { mutableStateOf(ComposeState()) },
|
||||
cxt = LocalContext.current,
|
||||
deleteMessage = { _, _ -> }
|
||||
)
|
||||
|
||||
@@ -135,8 +135,8 @@ struct ComposeView: View {
|
||||
.padding(.leading, 12)
|
||||
SendMessageView(
|
||||
composeState: $composeState,
|
||||
sendMessage: { text in
|
||||
sendMessage(text)
|
||||
sendMessage: {
|
||||
sendMessage()
|
||||
resetLinkPreview()
|
||||
},
|
||||
keyboardVisible: $keyboardVisible
|
||||
@@ -209,7 +209,7 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func sendMessage(_ text: String) {
|
||||
private func sendMessage() {
|
||||
logger.debug("ChatView sendMessage")
|
||||
Task {
|
||||
logger.debug("ChatView sendMessage: in Task")
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
|
||||
struct SendMessageView: View {
|
||||
@Binding var composeState: ComposeState
|
||||
var sendMessage: (String) -> Void
|
||||
var sendMessage: () -> Void
|
||||
@Namespace var namespace
|
||||
@FocusState.Binding var keyboardVisible: Bool
|
||||
@State private var teHeight: CGFloat = 42
|
||||
@@ -45,7 +45,7 @@ struct SendMessageView: View {
|
||||
.frame(width: 31, height: 31, alignment: .center)
|
||||
.padding([.bottom, .trailing], 3)
|
||||
} else {
|
||||
Button(action: { sendMessage(composeState.message) }) {
|
||||
Button(action: { sendMessage() }) {
|
||||
Image(systemName: composeState.editing() ? "checkmark.circle.fill" : "arrow.up.circle.fill")
|
||||
.resizable()
|
||||
.foregroundColor(.accentColor)
|
||||
@@ -90,7 +90,7 @@ struct SendMessageView_Previews: PreviewProvider {
|
||||
Spacer(minLength: 0)
|
||||
SendMessageView(
|
||||
composeState: $composeStateNew,
|
||||
sendMessage: { print ($0) },
|
||||
sendMessage: {},
|
||||
keyboardVisible: $keyboardVisible
|
||||
)
|
||||
}
|
||||
@@ -99,7 +99,7 @@ struct SendMessageView_Previews: PreviewProvider {
|
||||
Spacer(minLength: 0)
|
||||
SendMessageView(
|
||||
composeState: $composeStateEditing,
|
||||
sendMessage: { print ($0) },
|
||||
sendMessage: {},
|
||||
keyboardVisible: $keyboardVisible
|
||||
)
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ struct TerminalView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(_ cmdStr: String) {
|
||||
let cmd = ChatCommand.string(cmdStr)
|
||||
func sendMessage() {
|
||||
let cmd = ChatCommand.string(composeState.message)
|
||||
DispatchQueue.global().async {
|
||||
Task {
|
||||
composeState.inProgress = true
|
||||
|
||||
Reference in New Issue
Block a user