Talaria/app/src/main/java/dev/lonami/talaria/ui/screens/ChatScreen.kt

182 lines
5.8 KiB
Kotlin

package dev.lonami.talaria.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import dev.lonami.talaria.R
import dev.lonami.talaria.data.MockMessageRepository
import dev.lonami.talaria.ui.state.ChatViewModel
import dev.lonami.talaria.ui.theme.TalariaTheme
import kotlinx.coroutines.launch
import uniffi.talaria.Formatting
import uniffi.talaria.Message
import uniffi.talaria.TextFormat
@Composable
fun FormattedText(
text: String,
formatting: List<TextFormat>,
onFormatClicked: (TextFormat) -> Unit,
) {
val anno = AnnotatedString(text, spanStyles = formatting.map {
AnnotatedString.Range(SpanStyle(
fontWeight = when (it.format) {
Formatting.BOLD -> FontWeight.Bold
else -> FontWeight.Normal
},
fontStyle = when (it.format) {
Formatting.ITALIC -> FontStyle.Italic
else -> FontStyle.Normal
},
fontFamily = when (it.format) {
Formatting.CODE,
Formatting.PRE,
-> FontFamily.Monospace
else -> FontFamily.Default
},
textDecoration = when (it.format) {
Formatting.UNDERLINE -> TextDecoration.Underline
Formatting.STRIKE -> TextDecoration.LineThrough
else -> TextDecoration.None
},
color = when (it.format) {
Formatting.MENTION,
Formatting.HASH_TAG,
Formatting.BOT_COMMAND,
Formatting.URL,
Formatting.EMAIL,
Formatting.TEXT_URL,
Formatting.MENTION_NAME,
Formatting.PHONE,
Formatting.CASH_TAG,
-> Color(0xff0000ff)
else -> Color.Black
}), it.offset, it.offset + it.length)
})
ClickableText(
anno,
onClick = { offset ->
anno.spanStyles.indexOfFirst {
it.start <= offset && offset <= it.end
}.takeIf { it != -1 }?.also {
onFormatClicked(formatting[it])
}
}
)
}
@Composable
fun MessageCard(message: Message, modifier: Modifier = Modifier) {
Card(
elevation = 4.dp,
modifier = modifier
.fillMaxWidth()
.padding(8.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text(message.sender, fontWeight = FontWeight.Bold)
FormattedText(message.text, message.formatting, onFormatClicked = {})
}
}
}
@Composable
fun MessageList(messages: List<Message>, listState: LazyListState, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier, state = listState) {
items(messages.size) { MessageCard(messages[it]) }
}
}
@Composable
fun MessageInputField(
messageText: String,
onMessageChanged: (String) -> Unit,
onSendMessage: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(modifier = modifier) {
TextField(
messageText,
placeholder = { Text(stringResource(R.string.write_message)) },
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Send
),
keyboardActions = KeyboardActions(
onDone = { onSendMessage() }
),
modifier = Modifier.weight(1.0f),
onValueChange = onMessageChanged
)
Button(
onClick = onSendMessage
) {
Text(stringResource(R.string.send_message))
}
}
}
@Composable
fun ChatScreen(
selectedDialog: String,
modifier: Modifier = Modifier,
chatViewModel: ChatViewModel = viewModel(),
) {
val chatUiState by chatViewModel.uiState.collectAsState()
var messageText by remember { mutableStateOf("") }
val messageListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
Column(modifier = modifier.fillMaxSize()) {
MessageList(
chatUiState.messages,
modifier = Modifier.weight(1.0f),
listState = messageListState
)
MessageInputField(messageText, onMessageChanged = {
messageText = it
}, onSendMessage = {
chatViewModel.sendMessage(selectedDialog, messageText)
messageText = ""
coroutineScope.launch {
messageListState.animateScrollToItem(chatUiState.messages.size - 1)
}
})
}
}
@Preview
@Composable
fun ChatPreview() {
val viewModel = remember { ChatViewModel(MockMessageRepository()).apply { loadMessages("") } }
TalariaTheme {
ChatScreen("", chatViewModel = viewModel)
}
}