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, 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, 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) } }