From 4deb7470413a9d53323f6bedf26882ad2b0844a6 Mon Sep 17 00:00:00 2001 From: cel 🌸 Date: Fri, 11 Apr 2025 08:32:50 +0100 Subject: feat: nicks and avatars --- src/main.rs | 724 ++++++++++++++++++++-------------------------------- src/message_view.rs | 93 ++++--- 2 files changed, 321 insertions(+), 496 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 8b22ce3..0b8b3b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,9 +17,7 @@ use filamento::{roster::Contact, user::User, UpdateMessage}; use iced::alignment::Horizontal::Right; use iced::futures::{SinkExt, Stream, StreamExt}; use iced::keyboard::{on_key_press, on_key_release, Key, Modifiers}; -use iced::theme::palette::{ - Background, Danger, Extended, Pair, Primary, Secondary, Success, Warning, -}; +use iced::theme::palette::{Background, Danger, Extended, Pair, Primary, Secondary, Success}; use iced::theme::{Custom, Palette}; use iced::widget::button::Status; use iced::widget::text::{Fragment, IntoFragment, Wrapping}; @@ -70,92 +68,62 @@ impl Default for Config { pub struct Macaw { client: Account, config: Config, - // references users - messages: HashMap>>, - // references users - roster: HashMap, - // store count of how many things reference it. allows it to stay mutable. - // or maybe store a bool that indicates whether it can be garbage collected - // but then in that case, if you change the bool, then it can be dropped anyway.... - // realistically none of this stuff matters until there are group chats. and group chats will have a list of users anyway. - // so whenever a group chat is closed any users that are both not in the roster and that one doesn't also have a chat with - // can be dropped. - // but then also users who are no longer in the chat but were loaded because of old messages must also be dropped. - // so the set of users in the group chat must also include people who left, just marked as do-not-show/departed. solution! - // this only doesn't work if there are multiple group chats open at the same time ig. in this case the other chats' user - // lists would need to also be differenced. - // i'm pretty sure this is just O(2 + n) where n = number of other group chats open for each drop attempt, and it can - // happen in a separate thread in the background anyway so no slowdown. - // TODO: add presences reference - // references nothing, optionally contact - users: HashMap>>, - // chat could have no messages, and therefore no latest message. - // references users, latest message - chats: IndexMap>>, - subscription_requests: HashSet, - open_chat: Option, + presences: HashMap, + subscription_requests: HashSet, new_chat: Option, + // references chats, users, messages + open_chat: Option, + // references users, contacts + roster: HashMap, + // references chats, users, messages + chats_list: IndexMap, } -#[derive(Debug)] -pub struct MacawUser { - inner: User, - contact: Option>>, +#[derive(Debug, Clone)] +pub struct MacawMessage { + inner: ChatMessage, + from: MacawUser, } -impl Deref for MacawUser { - type Target = User; +impl Deref for MacawMessage { + type Target = ChatMessage; fn deref(&self) -> &Self::Target { &self.inner } } -impl DerefMut for MacawUser { +impl DerefMut for MacawMessage { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } -impl MacawUser { - pub fn contact(&self) -> Option> { - self.contact - .as_ref() - .map(|contact| contact.as_ref().borrow()) - } -} - #[derive(Debug, Clone)] -pub struct MacawMessage { - inner: ChatMessage, - user: Rc>, +pub struct MacawUser { + inner: User, + // contact not needed, as can always query the roster store to get this option. + // contact: Option, } -impl Deref for MacawMessage { - type Target = ChatMessage; +impl Deref for MacawUser { + type Target = User; fn deref(&self) -> &Self::Target { &self.inner } } -impl DerefMut for MacawMessage { +impl DerefMut for MacawUser { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } -impl MacawMessage { - pub fn user(&self) -> Ref<'_, MacawUser> { - let user = self.user.as_ref().borrow(); - user - } -} - -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MacawContact { inner: Contact, - user: Rc>, + user: User, } impl Deref for MacawContact { @@ -172,45 +140,44 @@ impl DerefMut for MacawContact { } } -impl MacawContact { - pub fn user(&self) -> Ref<'_, MacawUser> { - let user = self.user.as_ref().borrow(); - user - } -} - +#[derive(Debug, Clone)] pub struct MacawChat { inner: Chat, - user: Rc>, - message: Option>>, + user: MacawUser, } -impl Deref for MacawChat { - type Target = Chat; +pub struct ChatListItem { + // references chats + inner: MacawChat, + // references users, messages + latest_message: Option, +} + +impl Deref for ChatListItem { + type Target = MacawChat; fn deref(&self) -> &Self::Target { &self.inner } } -impl DerefMut for MacawChat { +impl DerefMut for ChatListItem { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } -impl MacawChat { - pub fn user(&self) -> Ref<'_, MacawUser> { - let user = self.user.as_ref().borrow(); - user +impl Deref for MacawChat { + type Target = Chat; + + fn deref(&self) -> &Self::Target { + &self.inner } +} - pub fn latest_message(&self) -> Option> { - let latest_message = self - .message - .as_ref() - .map(|message| message.as_ref().borrow()); - latest_message +impl DerefMut for MacawChat { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner } } @@ -228,13 +195,12 @@ impl Macaw { Self { client: account, config, - roster: HashMap::new(), - users: HashMap::new(), - chats: IndexMap::new(), + presences: HashMap::new(), subscription_requests: HashSet::new(), - open_chat: None, new_chat: None, - messages: HashMap::new(), + open_chat: None, + roster: HashMap::new(), + chats_list: IndexMap::new(), } } } @@ -486,35 +452,41 @@ async fn main() -> iced::Result { let luz_handle1 = luz_handle.clone(); let luz_handle2 = luz_handle.clone(); if cfg.auto_connect { - Task::batch( - [ - Task::batch([ - Task::perform( - async move { luz_handle1.get_roster_with_users().await }, - |result| { - let roster = result.unwrap(); - let mut macaw_roster = HashMap::new(); - for contact in roster { - macaw_roster.insert(contact.0.user_jid.clone(), contact); - } - Message::RosterWithUsers(macaw_roster) - }, - ), - Task::perform( - async move { - luz_handle2.get_chats_ordered_with_latest_messages().await - }, - |chats| { - let chats = chats.unwrap(); - info!("got chats: {:?}", chats); - Message::GotChats(chats) - }, - ), - ]) - .chain(Task::done(Message::Connect)), - Task::stream(stream), - ], - ) + Task::batch([ + Task::batch([ + Task::perform( + async move { luz_handle1.get_roster_with_users().await }, + |result| { + let roster = result.unwrap(); + let mut macaw_roster = HashMap::new(); + for (contact, user) in roster { + macaw_roster.insert( + contact.user_jid.clone(), + MacawContact { + inner: contact, + user, + }, + ); + } + Message::Roster(macaw_roster) + }, + ), + Task::perform( + async move { + luz_handle2 + .get_chats_ordered_with_latest_messages_and_users() + .await + }, + |chats| { + let chats = chats.unwrap(); + info!("got chats: {:?}", chats); + Message::GotChats(chats) + }, + ), + ]) + .chain(Task::done(Message::Connect)), + Task::stream(stream), + ]) } else { Task::batch([ Task::perform( @@ -522,14 +494,24 @@ async fn main() -> iced::Result { |result| { let roster = result.unwrap(); let mut macaw_roster = HashMap::new(); - for contact in roster { - macaw_roster.insert(contact.0.user_jid.clone(), contact); + for (contact, user) in roster { + macaw_roster.insert( + contact.user_jid.clone(), + MacawContact { + inner: contact, + user, + }, + ); } - Message::RosterWithUsers(macaw_roster) + Message::Roster(macaw_roster) }, ), Task::perform( - async move { luz_handle2.get_chats_ordered_with_latest_messages().await }, + async move { + luz_handle2 + .get_chats_ordered_with_latest_messages_and_users() + .await + }, |chats| { let chats = chats.unwrap(); info!("got chats: {:?}", chats); @@ -605,10 +587,10 @@ pub enum Message { LoginModal(login_modal::Message), ClientCreated(Client), Luz(UpdateMessage), - RosterWithUsers(HashMap), + Roster(HashMap), Connect, Disconnect, - GotChats(Vec<(Chat, ChatMessage)>), + GotChats(Vec<((Chat, User), (ChatMessage, User))>), ToggleChat(JID), SendMessage(JID, String), Error(Error), @@ -668,58 +650,21 @@ impl Macaw { presence: PresenceType::Online(online), }; client.connection_state = ConnectionState::Online; - let mut roster = HashMap::new(); - let mut get_users = Vec::new(); - for contact in vec { - if let Some(Some(user)) = - self.users.get(&contact.user_jid).map(|user| user.upgrade()) - { - let contact = MacawContact { - inner: contact, - user, - }; - roster.insert(contact.user_jid.clone(), contact); - user.borrow_mut().contact = Some(Rc::new(RefCell::new(contact))) - } else { - match self.client { - Account::LoggedIn(client) => get_users.push(Task::perform( - client.get_user(contact.user_jid), - |result| { - let result = result.unwrap(); - (contact, result) - }, - )), - Account::LoggedOut(login_modal) => {} - } - } - } - if get_users.is_empty() { - self.roster = roster; - Task::none() - } else { - // TODO: potential race condition if two rosters are gotten at the same time? - Task::batch(get_users).collect().then(|users| { - for (contact, user) in users { - let user = Rc::new(RefCell::new(MacawUser { - inner: user, - contact: None, - })); - let contact = MacawContact { + let roster = vec + .into_iter() + .map(|(contact, user)| { + ( + contact.user_jid.clone(), + MacawContact { inner: contact, user, - }; - roster.insert(contact.user_jid, contact); - user.borrow_mut().contact = - Some(Rc::new(RefCell::new(contact))); - self.users.insert( - contact.user_jid, - Rc::>::downgrade(&user), - ); - } - self.roster = roster; - Task::none() + }, + ) }) - } + .collect(); + // no need to also update users as any user updates will come in separately + self.roster = roster; + Task::none() } Account::LoggedOut(login_modal) => Task::none(), }, @@ -737,99 +682,15 @@ impl Macaw { Account::LoggedOut(login_modal) => Task::none(), } } - UpdateMessage::FullRoster(vec) => { - let mut roster = HashMap::new(); - let mut get_users = Vec::new(); - for contact in vec { - if let Some(Some(user)) = - self.users.get(&contact.user_jid).map(|user| user.upgrade()) - { - let contact = MacawContact { - inner: contact, - user, - }; - roster.insert(contact.user_jid.clone(), contact); - user.borrow_mut().contact = Some(Rc::new(RefCell::new(contact))) - } else { - match self.client { - Account::LoggedIn(client) => get_users.push(Task::perform( - client.get_user(contact.user_jid), - |result| { - let result = result.unwrap(); - (contact, result) - }, - )), - Account::LoggedOut(login_modal) => {} - } - } - } - if get_users.is_empty() { - self.roster = roster; - Task::none() - } else { - // TODO: potential race condition if two rosters are gotten at the same time? - Task::batch(get_users).collect().then(|users| { - for (contact, user) in users { - let user = Rc::new(RefCell::new(MacawUser { - inner: user, - contact: None, - })); - let contact = MacawContact { - inner: contact, - user, - }; - roster.insert(contact.user_jid, contact); - user.borrow_mut().contact = Some(Rc::new(RefCell::new(contact))); - self.users.insert( - contact.user_jid, - Rc::>::downgrade(&user), - ); - } - self.roster = roster; - Task::none() - }) - } - } - UpdateMessage::RosterUpdate(contact) => { - if let Some(Some(user)) = - self.users.get(&contact.user_jid).map(|user| user.upgrade()) - { - let contact = MacawContact { + UpdateMessage::RosterUpdate(contact, user) => { + self.roster.insert( + contact.user_jid.clone(), + MacawContact { inner: contact, user, - }; - self.roster.insert(contact.user_jid.clone(), contact); - user.borrow_mut().contact = Some(Rc::new(RefCell::new(contact))); - Task::none() - } else { - match self.client { - Account::LoggedIn(client) => { - Task::perform(client.get_user(contact.user_jid), |result| { - let result = result.unwrap(); - (contact, result) - }) - .then(|(contact, user)| { - let user = Rc::new(RefCell::new(MacawUser { - inner: user, - contact: None, - })); - let contact = MacawContact { - inner: contact, - user, - }; - self.roster.insert(contact.user_jid.clone(), contact); - user.borrow_mut().contact = - Some(Rc::new(RefCell::new(contact))); - self.users.insert( - contact.user_jid, - Rc::>::downgrade(&user), - ); - Task::none() - }) - } - Account::LoggedOut(login_modal) => Task::none(), - } - } + }, + ); + Task::none() } UpdateMessage::RosterDelete(jid) => { self.roster.remove(&jid); @@ -837,123 +698,98 @@ impl Macaw { } UpdateMessage::Presence { from, presence } => { // TODO: presence handling + self.presences.insert(from, presence); Task::none() } - UpdateMessage::Message { to, message } => { - if let Some(Some(user)) = - self.users.get(&message.from).map(|user| user.upgrade()) + UpdateMessage::Message { to, message, from } => { + let message = MacawMessage { + inner: message, + from: MacawUser { inner: from }, + }; + if let Some((chat_jid, mut chat_list_item)) = + self.chats_list.shift_remove_entry(&to) { - let message = MacawMessage { - inner: message, - user, - }; - let message = Rc::new(RefCell::new(message)); - self.messages.insert( - message.as_ref().borrow().id, - Rc::>::downgrade(&message), - ); - if let Some((chat_jid, chat)) = self.chats.shift_remove_entry(&to) { - chat.as_ref().borrow_mut().message = Some(message); - self.chats.insert_before(0, chat_jid, chat); - if let Some(open_chat) = &mut self.open_chat { - if open_chat.chat().user().jid == to { - open_chat.messages.push(message); - } + chat_list_item.latest_message = Some(message.clone()); + self.chats_list.insert_before(0, chat_jid, chat_list_item); + if let Some(open_chat) = &mut self.open_chat { + if open_chat.chat.user.jid == to { + open_chat.update(message_view::Message::Message(message)); } - } else { - let chat = Chat { - correspondent: to.clone(), - // TODO: should have a new chat event first... - have_chatted: false, - }; - let chat = MacawChat { - inner: chat, - user, - message: Some(message), - }; - self.chats.insert_before(0, to, Rc::new(RefCell::new(chat))); } - Task::none() } else { - match self.client { - Account::LoggedIn(client) => { - Task::perform(client.get_user(message.from), |result| { - let result = result.unwrap(); - result - }) - .then(|user| { - let user = Rc::new(RefCell::new(MacawUser { - inner: user, - contact: None, - })); - self.users.insert( - user.as_ref().borrow().jid, - Rc::>::downgrade(&user), - ); - let message = MacawMessage { - inner: message, - user, - }; - let message = Rc::new(RefCell::new(message)); - self.messages.insert( - message.as_ref().borrow().id, - Rc::>::downgrade(&message), - ); - if let Some((chat_jid, chat)) = - self.chats.shift_remove_entry(&to) - { - chat.as_ref().borrow_mut().message = Some(message); - self.chats.insert_before(0, chat_jid, chat); - if let Some(open_chat) = &mut self.open_chat { - if open_chat.chat().user().jid == to { - open_chat.messages.push(message); - } - } - } else { - let chat = Chat { - correspondent: to.clone(), - // TODO: should have a new chat event first... - have_chatted: false, - }; - let chat = MacawChat { - inner: chat, - user, - message: Some(message), - }; - self.chats.insert_before( - 0, - to, - Rc::new(RefCell::new(chat)), - ); - } - Task::none() - }) - } - Account::LoggedOut(login_modal) => Task::none(), - } + // TODO: get the actual chat from the thing, or send the chat first, from the client side. + let chat = Chat { + correspondent: to.clone(), + have_chatted: false, + }; + let chat_list_item = ChatListItem { + inner: MacawChat { + inner: chat, + user: message.from.clone(), + }, + latest_message: Some(message), + }; + self.chats_list.insert_before(0, to, chat_list_item); } + Task::none() } UpdateMessage::SubscriptionRequest(jid) => { // TODO: subscription requests Task::none() } - UpdateMessage::MessageDelivery { id, delivery } => { - if let Some(Some(message)) = - self.messages.get(&id).map(|message| message.upgrade()) - { - message.as_ref().borrow_mut().delivery = Some(delivery) + UpdateMessage::MessageDelivery { chat, id, delivery } => { + if let Some(chat_list_item) = self.chats_list.get_mut(&chat) { + if let Some(latest_message) = &mut chat_list_item.latest_message { + if latest_message.id == id { + latest_message.delivery = Some(delivery) + } + } + } + if let Some(open_chat) = &mut self.open_chat { + if let Some(message) = open_chat.messages.get_mut(&id) { + message.delivery = Some(delivery) + } } Task::none() } UpdateMessage::NickChanged { jid, nick } => { - if let Some(Some(user)) = self.users.get(&jid).map(|user| user.upgrade()) { - user.as_ref().borrow_mut().nick = nick + // roster, chats_list, open chat + if let Some(contact) = self.roster.get_mut(&jid) { + contact.user.nick = nick.clone(); + } + if let Some(chats_list_item) = self.chats_list.get_mut(&jid) { + chats_list_item.user.nick = nick.clone() + } + if let Some(open_chat) = &mut self.open_chat { + for (_, message) in &mut open_chat.messages { + if message.from.jid == jid { + message.from.nick = nick.clone() + } + } + if open_chat.chat.user.jid == jid { + open_chat.chat.user.nick = nick + } } Task::none() } UpdateMessage::AvatarChanged { jid, id } => { - if let Some(Some(user)) = self.users.get(&jid).map(|user| user.upgrade()) { - user.as_ref().borrow_mut().avatar = id + // roster, chats_list, open chat + if let Some(contact) = self.roster.get_mut(&jid) { + contact.user.avatar = id.clone(); + } + if let Some(chats_list_item) = self.chats_list.get_mut(&jid) { + chats_list_item.user.avatar = id.clone() + } + if let Some(open_chat) = &mut self.open_chat { + // TODO: consider using an indexmap with two keys for speeding this up? + for (_, message) in &mut open_chat.messages { + if message.from.jid == jid { + message.from.avatar = id.clone() + } + } + if open_chat.chat.user.jid == jid { + open_chat.chat.user.avatar = id + } } Task::none() } @@ -970,17 +806,24 @@ impl Macaw { |result| { let roster = result.unwrap(); let mut macaw_roster = HashMap::new(); - for contact in roster { - macaw_roster.insert(contact.0.user_jid.clone(), contact); + for (contact, user) in roster { + macaw_roster.insert( + contact.user_jid.clone(), + MacawContact { + inner: contact, + user, + }, + ); } - Message::RosterWithUsers(macaw_roster) + // TODO: clean this up + Message::Roster(macaw_roster) }, ), Task::perform( async move { client2 .client - .get_chats_ordered_with_latest_messages() + .get_chats_ordered_with_latest_messages_and_users() .await }, |chats| { @@ -1002,17 +845,23 @@ impl Macaw { |result| { let roster = result.unwrap(); let mut macaw_roster = HashMap::new(); - for contact in roster { - macaw_roster.insert(contact.0.user_jid.clone(), contact); + for (contact, user) in roster { + macaw_roster.insert( + contact.user_jid.clone(), + MacawContact { + inner: contact, + user, + }, + ); } - Message::RosterWithUsers(macaw_roster) + Message::Roster(macaw_roster) }, ), Task::perform( async move { client2 .client - .get_chats_ordered_with_latest_messages() + .get_chats_ordered_with_latest_messages_and_users() .await }, |chats| { @@ -1028,22 +877,8 @@ impl Macaw { ]) } } - Message::RosterWithUsers(hash_map) => { - for (_, (contact, user)) in hash_map { - let user = MacawUser { - inner: user, - contact: None, - }; - let user = Rc::new(RefCell::new(user)); - let contact = MacawContact { - inner: contact, - user, - }; - self.roster.insert(contact.user_jid, contact); - user.borrow_mut().contact = Some(Rc::new(RefCell::new(contact))); - self.users - .insert(contact.user_jid, Rc::>::downgrade(&user)); - } + Message::Roster(hash_map) => { + self.roster = hash_map; Task::none() } Message::Connect => match &mut self.client { @@ -1070,47 +905,34 @@ impl Macaw { Message::ToggleChat(jid) => { match &self.open_chat { Some(message_view) => { - if message_view.chat().user().jid == jid { + if message_view.chat.user.jid == jid { self.open_chat = None; return Task::none(); } } None => {} } - if let Some(chat) = self.chats.get(&jid) { + if let Some(chat) = self.chats_list.get(&jid) { + self.open_chat = Some(MessageView::new((*chat).clone(), &self.config)); match &self.client { Account::LoggedIn(client) => { let client = client.clone(); Task::perform( - async move { client.get_messages(jid).await }, + async move { client.get_messages_with_users(jid).await }, move |result| { let message_history = result.unwrap(); - let messages = Vec::new(); - for message in message_history { - // TODO: there must be users for the messages, but won't work for group chats. - let user = self - .users - .get(&message.from) - .unwrap() - .upgrade() - .unwrap(); - let message = MacawMessage { + let messages = message_history + .into_iter() + .map(|(message, user)| MacawMessage { inner: message, - user, - }; - let message = Rc::new(RefCell::new(message)); - self.messages.insert( - message.as_ref().borrow().id, - Rc::>::downgrade(&message), - ); - messages.push(message) - } - let open_chat = MessageView::new(chat.clone(), &self.config); - open_chat.messages = messages; - self.open_chat = Some(open_chat); + from: MacawUser { inner: user }, + }) + .collect(); + Message::MessageView(message_view::Message::MessageHistory( + messages, + )) }, ) - .discard() } Account::LoggedOut(login_modal) => Task::none(), } @@ -1203,14 +1025,23 @@ impl Macaw { return Task::none(); } }; - for (chat, message) in chats { + for ((chat, chat_user), (message, message_user)) in chats { let chat = MacawChat { - inner: todo!(), - user: todo!(), - message: todo!(), - } - self.chats - .insert(chat.0.correspondent.clone(), (chat.0.clone(), Some(chat.1))); + inner: chat, + user: MacawUser { inner: chat_user }, + }; + let latest_message = MacawMessage { + inner: message, + from: MacawUser { + inner: message_user, + }, + }; + let chat_list_item = ChatListItem { + inner: chat.clone(), + latest_message: Some(latest_message), + }; + self.chats_list + .insert(chat.correspondent.clone(), chat_list_item); } Task::batch(tasks) } @@ -1238,10 +1069,9 @@ impl Macaw { let action = message_view.update(message); match action { message_view::Action::None => Task::none(), - message_view::Action::SendMessage(m) => Task::done(Message::SendMessage( - message_view.chat().user().jid.clone(), - m, - )), + message_view::Action::SendMessage(m) => { + Task::done(Message::SendMessage(message_view.chat.user.jid.clone(), m)) + } } } else { Task::none() @@ -1268,15 +1098,15 @@ impl Macaw { let mut ui: Element = { let mut chats_list: Column = column![]; if let Account::LoggedIn(client) = &self.client { - for (jid, chat) in &self.chats { + for (jid, chat) in &self.chats_list { let mut open = false; if let Some(open_chat) = &self.open_chat { - if open_chat.chat().user().jid == *jid { + if open_chat.chat.user.jid == *jid { open = true; } } let chat_list_item = - chat_list_item(client.files_root(), chat.as_ref().borrow(), open); + chat_list_item(&self.roster, client.files_root(), chat, open); chats_list = chats_list.push(chat_list_item); } } @@ -1345,10 +1175,6 @@ impl Macaw { color: color!(0x392c25), text: color!(0xdcdcdc), }, - weakest: Pair { - color: color!(0xdcdcdc), - text: color!(0x392c25), - }, weak: Pair { color: color!(0xdcdcdc), text: color!(0x392c25), @@ -1357,10 +1183,6 @@ impl Macaw { color: color!(0x364b3b), text: color!(0xdcdcdc), }, - strongest: Pair { - color: color!(0x364b3b), - text: color!(0xdcdcdc), - }, }, primary: Primary { base: Pair { @@ -1404,20 +1226,6 @@ impl Macaw { text: color!(0xdcdcdc), }, }, - warning: Warning { - base: Pair { - color: color!(0xFF9D00), - text: color!(0x000000), - }, - weak: Pair { - color: color!(0xFF9D00), - text: color!(0x000000), - }, - strong: Pair { - color: color!(0xFF9D00), - text: color!(0x000000), - }, - }, danger: Danger { base: Pair { color: color!(0xC1173C), @@ -1479,28 +1287,33 @@ where stack![base.into(), opaque(mouse_area)].into() } -fn chat_list_item<'a, C>(file_root: &'a Path, chat: C, open: bool) -> Element<'a, Message> -where - C: Deref + 'a, -{ +fn chat_list_item<'a>( + roster: &HashMap, + file_root: &'a Path, + chat_list_item: &'a ChatListItem, + open: bool, +) -> Element<'a, Message> { let name: String; - if let Some(Some(contact_name)) = chat.user().contact().map(|contact| contact.name.clone()) { - name = contact_name - } else if let Some(nick) = &chat.user().nick { + if let Some(Some(contact_name)) = roster + .get(chat_list_item.correspondent()) + .map(|contact| &contact.name) + { + name = contact_name.clone() + } else if let Some(nick) = &chat_list_item.user.nick { name = nick.clone() } else { - name = chat.correspondent().to_string(); + name = chat_list_item.correspondent().to_string(); } let avatar: Option; - if let Some(user_avatar) = &chat.user().avatar { + if let Some(user_avatar) = &chat_list_item.user.avatar { avatar = Some(user_avatar.clone()) } else { avatar = None } let latest_message_text: Option<(String, String)>; - if let Some(latest_message) = chat.latest_message() { + if let Some(latest_message) = &chat_list_item.latest_message { let message = latest_message.body.body.replace("\n", " "); let date = latest_message.timestamp.naive_local(); let now = Local::now().naive_local(); @@ -1527,7 +1340,9 @@ where } let avatar_image = if let Some(avatar) = avatar { - let path = file_root.join(avatar); + let mut path = file_root.join(avatar); + path.set_extension("jpg"); + info!("got avatar: {:?}", path); Some(image(path).width(48).height(48)) } else { None @@ -1570,7 +1385,8 @@ where text(name).into() } }; - let mut button = button(content).on_press(Message::ToggleChat(chat.correspondent.clone())); + let mut button = + button(content).on_press(Message::ToggleChat(chat_list_item.correspondent().clone())); if open { button = button.style(|theme: &Theme, status| { let palette = theme.extended_palette(); diff --git a/src/message_view.rs b/src/message_view.rs index 4ebda83..e936b69 100644 --- a/src/message_view.rs +++ b/src/message_view.rs @@ -1,54 +1,27 @@ -use std::{ - borrow::{Borrow, Cow}, - cell::{Ref, RefCell}, - ops::Deref, - rc::Rc, -}; - use chrono::NaiveDate; -use filamento::chat::Message as ChatMessage; use iced::{ - alignment::Horizontal::{self, Right}, border::Radius, - color, - theme::Palette, - widget::{ - button, column, container, horizontal_space, row, scrollable, text, text_editor, - text_editor::Content, text_input, Column, - }, + widget::{button, column, container, row, scrollable, text, text_editor, text_editor::Content}, Border, Color, Element, Length::{Fill, Shrink}, Theme, }; use indexmap::IndexMap; -use jid::JID; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{MacawChat, MacawMessage}; pub struct MessageView { + // references chats, users + pub chat: MacawChat, + // references users, messages + pub messages: IndexMap, pub config: Config, - pub chat: Rc>, - pub messages: Vec>>, pub new_message: Content, pub shift_pressed: bool, } -impl MessageView { - pub fn chat(&self) -> Ref<'_, MacawChat> { - self.chat.as_ref().borrow() - } - - pub fn messages( - &self, - ) -> impl Iterator + use<'_>> + use<'_> { - self.messages - .iter() - .map(|message| message.as_ref().borrow()) - } -} - #[derive(Serialize, Deserialize, Clone)] pub struct Config { pub send_on_enter: bool, @@ -64,8 +37,8 @@ impl Default for Config { #[derive(Debug, Clone)] pub enum Message { - // MessageHistory(Vec>>), - // Message(Rc>), + MessageHistory(Vec), + Message(MacawMessage), MessageCompose(text_editor::Action), SendMessage(String), } @@ -76,11 +49,11 @@ pub enum Action { } impl MessageView { - pub fn new(chat: Rc>, config: &super::Config) -> Self { + pub fn new(chat: MacawChat, config: &super::Config) -> Self { Self { chat, // TODO: save position in message history - messages: Vec::new(), + messages: IndexMap::new(), // TODO: save draft (as part of chat struct?) new_message: Content::new(), config: config.message_view_config.clone(), @@ -120,13 +93,52 @@ impl MessageView { self.new_message = Content::new(); Action::SendMessage(m) } + Message::MessageHistory(macaw_messages) => { + if self.messages.is_empty() { + self.messages = macaw_messages + .into_iter() + .map(|message| (message.id, message)) + .collect() + } else { + for message in macaw_messages { + let index = match self + .messages + .binary_search_by(|_, value| value.timestamp.cmp(&message.timestamp)) + { + Ok(i) => i, + Err(i) => i, + }; + self.messages.insert_before(index, message.id, message); + } + } + Action::None + } + Message::Message(macaw_message) => { + if let Some((_, last)) = self.messages.last() { + if last.timestamp < macaw_message.timestamp { + self.messages.insert(macaw_message.id, macaw_message); + } else { + let index = match self.messages.binary_search_by(|_, value| { + value.timestamp.cmp(&macaw_message.timestamp) + }) { + Ok(i) => i, + Err(i) => i, + }; + self.messages + .insert_before(index, macaw_message.id, macaw_message); + } + } else { + self.messages.insert(macaw_message.id, macaw_message); + } + Action::None + } } } pub fn view(&self) -> Element { let mut messages_view = column![].spacing(8).padding(8); let mut latest_date = NaiveDate::MIN; - for message in self.messages() { + for (_id, message) in &self.messages { let message_date = message.timestamp.naive_local().date(); if message_date > latest_date { latest_date = message_date; @@ -154,14 +166,11 @@ impl MessageView { .into() } - pub fn message<'a, 'b, M>(&'a self, message: M) -> Element<'b, Message> - where - M: Deref + 'b, - { + pub fn message<'a>(&'a self, message: &MacawMessage) -> Element<'a, Message> { let timestamp = message.timestamp.naive_local(); let timestamp = timestamp.time().format("%H:%M").to_string(); - if self.chat().user().jid == message.user().jid { + if self.chat.user.jid == message.from.jid { container( container( column![ -- cgit