Load messages from API

This commit is contained in:
Lonami Exo 2022-10-29 13:06:38 +02:00
parent 43421f6e54
commit d350d1a048
8 changed files with 232 additions and 31 deletions

View File

@ -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 = {

View File

@ -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<Message> {
return generateSequence {
Message("Alice", "Testing")
}.take(50).toList()
interface MessageRepository {
fun loadMessages(chat: String): List<Message>
fun sendMessage(chat: String, message: String): Message
}
class NativeMessageRepository : MessageRepository {
override fun loadMessages(chat: String): List<Message> {
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<Message> {
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(),
)
}
}

View File

@ -1,3 +0,0 @@
package dev.lonami.talaria.models
data class Message(val sender: String, val text: String)

View File

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

View File

@ -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<Message> = mutableListOf())

View File

@ -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<ChatUiState> = _uiState.asStateFlow()
private fun loadMessages() {
_uiState.value = ChatUiState(MessageRepository.loadMessages().toMutableList())
fun loadMessages(chat: String) {
_uiState.update {
it.messages.clear()
it.messages.addAll(repository.loadMessages(chat))
it
}
}
fun sendMessage(dialog: String, message: String) {
sendMessage(dialog, message)
_uiState.update { state ->
state.messages.add(Message("You", message))
state
val sent = repository.sendMessage(dialog, message)
_uiState.update {
it.messages.add(sent)
it
}
}
init {
loadMessages()
}
}

View File

@ -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<String>,
}
#[derive(Debug, Clone)]
pub struct Message {
id: i32,
sender: String,
text: String,
date: SystemTime,
edit_date: Option<SystemTime>,
formatting: Vec<TextFormat>,
}
#[derive(Debug, Clone)]
pub struct MessagePreview {
sender: String,
@ -323,9 +365,87 @@ pub fn get_dialogs() -> Result<Vec<Dialog>> {
})
}
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<Vec<Message>> {
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<Message> {
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)
}

View File

@ -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<TextFormat> formatting;
};
dictionary MessagePreview {
string sender;
string text;
@ -43,5 +82,7 @@ namespace talaria {
[Throws=NativeError]
sequence<Dialog> get_dialogs();
[Throws=NativeError]
void send_message(string packed, string text);
sequence<Message> get_messages(string packed);
[Throws=NativeError]
Message send_message(string packed, string text);
};