diff --git a/app/src/main/java/dev/lonami/talaria/TalariaApp.kt b/app/src/main/java/dev/lonami/talaria/TalariaApp.kt index d8189e9..676a30e 100644 --- a/app/src/main/java/dev/lonami/talaria/TalariaApp.kt +++ b/app/src/main/java/dev/lonami/talaria/TalariaApp.kt @@ -21,6 +21,7 @@ import androidx.navigation.compose.rememberNavController import dev.lonami.talaria.ui.screens.ChatScreen import dev.lonami.talaria.ui.screens.DialogScreen import dev.lonami.talaria.ui.screens.LoginScreen +import dev.lonami.talaria.ui.state.ChatViewModel import kotlinx.coroutines.launch import uniffi.talaria.needLogin @@ -163,6 +164,8 @@ fun TalariaApp(modifier: Modifier = Modifier) { } } + val chatViewModel = remember { ChatViewModel() } + Scaffold( modifier = modifier, topBar = { @@ -193,11 +196,12 @@ fun TalariaApp(modifier: Modifier = Modifier) { composable(route = TalariaScreen.Dialog.name) { DialogScreen(onDialogSelected = { selectedDialog = it + chatViewModel.loadMessages(it) navController.navigate(TalariaScreen.Chat.name) }) } composable(route = TalariaScreen.Chat.name) { - ChatScreen(selectedDialog) + ChatScreen(selectedDialog, chatViewModel = chatViewModel) } composable(route = TalariaScreen.Login.name) { LoginScreen(onConfirmOtp = { diff --git a/app/src/main/java/dev/lonami/talaria/data/MessageRepository.kt b/app/src/main/java/dev/lonami/talaria/data/MessageRepository.kt index 3e97ddd..ee6f41c 100644 --- a/app/src/main/java/dev/lonami/talaria/data/MessageRepository.kt +++ b/app/src/main/java/dev/lonami/talaria/data/MessageRepository.kt @@ -1,11 +1,46 @@ package dev.lonami.talaria.data -import dev.lonami.talaria.models.Message +import uniffi.talaria.Message +import uniffi.talaria.getMessages +import java.time.Instant -object MessageRepository { - fun loadMessages(): List { - return generateSequence { - Message("Alice", "Testing") - }.take(50).toList() +interface MessageRepository { + fun loadMessages(chat: String): List + fun sendMessage(chat: String, message: String): Message +} + +class NativeMessageRepository : MessageRepository { + override fun loadMessages(chat: String): List { + return getMessages(chat) + } + + override fun sendMessage(chat: String, message: String): Message { + return uniffi.talaria.sendMessage(chat, message) + } +} + +class MockMessageRepository(private var msgCounter: Int = 50) : MessageRepository { + override fun loadMessages(chat: String): List { + return (0 until 50).map { + Message( + id = it, + sender = "Alice", + text = "Testing", + date = Instant.now(), + editDate = null, + formatting = listOf(), + ) + }.toList() + } + + override fun sendMessage(chat: String, message: String): Message { + return Message( + id = msgCounter++, + sender = "You", + text = message, + date = Instant.now(), + editDate = null, + formatting = listOf(), + ) } } diff --git a/app/src/main/java/dev/lonami/talaria/models/Message.kt b/app/src/main/java/dev/lonami/talaria/models/Message.kt deleted file mode 100644 index 2192574..0000000 --- a/app/src/main/java/dev/lonami/talaria/models/Message.kt +++ /dev/null @@ -1,3 +0,0 @@ -package dev.lonami.talaria.models - -data class Message(val sender: String, val text: String) diff --git a/app/src/main/java/dev/lonami/talaria/ui/screens/ChatScreen.kt b/app/src/main/java/dev/lonami/talaria/ui/screens/ChatScreen.kt index 4583348..fef1bbb 100644 --- a/app/src/main/java/dev/lonami/talaria/ui/screens/ChatScreen.kt +++ b/app/src/main/java/dev/lonami/talaria/ui/screens/ChatScreen.kt @@ -19,10 +19,11 @@ 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.models.Message +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.Message @Composable fun MessageCard(message: Message, modifier: Modifier = Modifier) { @@ -110,7 +111,9 @@ fun ChatScreen( @Preview @Composable fun ChatPreview() { + val viewModel = remember { ChatViewModel(MockMessageRepository()).apply { loadMessages("") } } + TalariaTheme { - ChatScreen("") + ChatScreen("", chatViewModel = viewModel) } } diff --git a/app/src/main/java/dev/lonami/talaria/ui/state/ChatUiState.kt b/app/src/main/java/dev/lonami/talaria/ui/state/ChatUiState.kt index 9aaa58d..51bd059 100644 --- a/app/src/main/java/dev/lonami/talaria/ui/state/ChatUiState.kt +++ b/app/src/main/java/dev/lonami/talaria/ui/state/ChatUiState.kt @@ -1,5 +1,5 @@ package dev.lonami.talaria.ui.state -import dev.lonami.talaria.models.Message +import uniffi.talaria.Message data class ChatUiState(val messages: MutableList = mutableListOf()) diff --git a/app/src/main/java/dev/lonami/talaria/ui/state/ChatViewModel.kt b/app/src/main/java/dev/lonami/talaria/ui/state/ChatViewModel.kt index 1cabbd9..0608891 100644 --- a/app/src/main/java/dev/lonami/talaria/ui/state/ChatViewModel.kt +++ b/app/src/main/java/dev/lonami/talaria/ui/state/ChatViewModel.kt @@ -2,29 +2,30 @@ package dev.lonami.talaria.ui.state import androidx.lifecycle.ViewModel import dev.lonami.talaria.data.MessageRepository -import dev.lonami.talaria.models.Message +import dev.lonami.talaria.data.NativeMessageRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -class ChatViewModel : ViewModel() { +class ChatViewModel(private val repository: MessageRepository = NativeMessageRepository()) : + ViewModel() { private val _uiState = MutableStateFlow(ChatUiState()) val uiState: StateFlow = _uiState.asStateFlow() - private fun loadMessages() { - _uiState.value = ChatUiState(MessageRepository.loadMessages().toMutableList()) - } - - fun sendMessage(dialog: String, message: String) { - sendMessage(dialog, message) - _uiState.update { state -> - state.messages.add(Message("You", message)) - state + fun loadMessages(chat: String) { + _uiState.update { + it.messages.clear() + it.messages.addAll(repository.loadMessages(chat)) + it } } - init { - loadMessages() + fun sendMessage(dialog: String, message: String) { + val sent = repository.sendMessage(dialog, message) + _uiState.update { + it.messages.add(sent) + it + } } } diff --git a/native/src/lib.rs b/native/src/lib.rs index d228272..ca89e75 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -3,7 +3,7 @@ mod db; -use grammers_client::types::LoginToken; +use grammers_client::types::{LoginToken, Message as OgMessage}; use grammers_client::{Client, Config, InitParams}; use grammers_session::{PackedChat, Session, UpdateState}; use grammers_tl_types as tl; @@ -75,6 +75,48 @@ pub enum MessageAck { Sent, } +#[derive(Debug, Clone, Copy)] +pub enum Formatting { + Unknown, + Mention, + HashTag, + BotCommand, + Url, + Email, + Bold, + Italic, + Code, + Pre, + TextUrl, + MentionName, + Phone, + CashTag, + Underline, + Strike, + Blockquote, + BankCard, + Spoiler, + CustomEmoji, +} + +#[derive(Debug, Clone)] +pub struct TextFormat { + format: Formatting, + offset: i32, + length: i32, + extra: Option, +} + +#[derive(Debug, Clone)] +pub struct Message { + id: i32, + sender: String, + text: String, + date: SystemTime, + edit_date: Option, + formatting: Vec, +} + #[derive(Debug, Clone)] pub struct MessagePreview { sender: String, @@ -323,9 +365,87 @@ pub fn get_dialogs() -> Result> { }) } -pub fn send_message(packed: String, text: String) -> Result<()> { +fn adapt_message(m: OgMessage) -> Message { + Message { + id: m.id(), + sender: if let Some(sender) = m.sender() { + sender.name().to_string() + } else { + "unknown".to_string() + }, + text: m.text().to_string(), + date: m.date().into(), + edit_date: m.edit_date().map(|d| d.into()), + formatting: if let Some(entities) = m.fmt_entities() { + use tl::enums::MessageEntity as ME; + + macro_rules! tf { + ($formatting:ident($entity:ident)) => { + tf!($formatting($entity).extra(None)) + }; + ($formatting:ident($entity:ident).extra($extra:expr)) => { + TextFormat { + format: Formatting::$formatting, + offset: $entity.offset, + length: $entity.length, + extra: $extra, + } + }; + } + + entities + .into_iter() + .map(|e| match e { + ME::Unknown(e) => tf!(Unknown(e)), + ME::Mention(e) => tf!(Mention(e)), + ME::Hashtag(e) => tf!(HashTag(e)), + ME::BotCommand(e) => tf!(BotCommand(e)), + ME::Url(e) => tf!(Url(e)), + ME::Email(e) => tf!(Email(e)), + ME::Bold(e) => tf!(Bold(e)), + ME::Italic(e) => tf!(Italic(e)), + ME::Code(e) => tf!(Code(e)), + ME::Pre(e) => tf!(Pre(e).extra(Some(e.language.to_string()))), + ME::TextUrl(e) => tf!(TextUrl(e).extra(Some(e.url.to_string()))), + ME::MentionName(e) => tf!(MentionName(e).extra(Some(e.user_id.to_string()))), + ME::InputMessageEntityMentionName(e) => tf!(Unknown(e)), + ME::Phone(e) => tf!(Phone(e)), + ME::Cashtag(e) => tf!(CashTag(e)), + ME::Underline(e) => tf!(Underline(e)), + ME::Strike(e) => tf!(Strike(e)), + ME::Blockquote(e) => tf!(Blockquote(e)), + ME::BankCard(e) => tf!(BankCard(e)), + ME::Spoiler(e) => tf!(Spoiler(e)), + ME::CustomEmoji(e) => { + tf!(CustomEmoji(e).extra(Some(e.document_id.to_string()))) + } + }) + .collect() + } else { + Vec::new() + }, + } +} + +pub fn get_messages(packed: String) -> Result> { let chat = PackedChat::from_hex(&packed).unwrap(); let client = CLIENT.get().ok_or(NativeError::Initialization)?; - block_on(client.send_message(chat, text)).map_err(|_| NativeError::Network)?; - Ok(()) + + block_on(async { + let mut result = Vec::new(); + let mut messages = client.iter_messages(chat); + while let Some(message) = messages.next().await.map_err(|_| NativeError::Network)? { + result.push(message); + } + Ok(result) + }) + .map(|messages| messages.into_iter().map(adapt_message).collect()) +} + +pub fn send_message(packed: String, text: String) -> Result { + let chat = PackedChat::from_hex(&packed).unwrap(); + let client = CLIENT.get().ok_or(NativeError::Initialization)?; + block_on(client.send_message(chat, text)) + .map(adapt_message) + .map_err(|_| NativeError::Network) } diff --git a/native/src/talaria.udl b/native/src/talaria.udl index 9532124..fc7fe45 100644 --- a/native/src/talaria.udl +++ b/native/src/talaria.udl @@ -11,6 +11,45 @@ enum MessageAck { "Sent", }; +enum Formatting { + "Unknown", + "Mention", + "HashTag", + "BotCommand", + "Url", + "Email", + "Bold", + "Italic", + "Code", + "Pre", // language:string + "TextUrl", // url:string + "MentionName", // user_id:long + "Phone", + "CashTag", + "Underline", + "Strike", + "Blockquote", + "BankCard", + "Spoiler", + "CustomEmoji", // document_id:long +}; + +dictionary TextFormat { + Formatting format; + i32 offset; + i32 length; + string? extra; +}; + +dictionary Message { + i32 id; + string sender; + string text; + timestamp date; + timestamp? edit_date; + sequence formatting; +}; + dictionary MessagePreview { string sender; string text; @@ -43,5 +82,7 @@ namespace talaria { [Throws=NativeError] sequence get_dialogs(); [Throws=NativeError] - void send_message(string packed, string text); + sequence get_messages(string packed); + [Throws=NativeError] + Message send_message(string packed, string text); };