use std::collections::{HashMap, HashSet}; use iced::futures::{SinkExt, Stream, StreamExt}; use iced::widget::{button, column, row, text, text_input}; use iced::{stream, Element, Subscription, Task, Theme}; use jid::JID; use luz::chat::{Chat, Message as ChatMessage}; use luz::presence::{Offline, Presence}; use luz::CommandMessage; use luz::{roster::Contact, user::User, LuzHandle, UpdateMessage}; use tokio::sync::oneshot; use tokio_stream::wrappers::ReceiverStream; #[derive(Default)] pub struct Macaw { client: Option, roster: HashMap, users: HashMap, presences: HashMap, chats: HashMap)>, subscription_requests: HashSet, connection_status: Option, } fn main() -> iced::Result { tracing_subscriber::fmt::init(); iced::application("Macaw", Macaw::update, Macaw::view) .subscription(Macaw::subscription) .run() } #[derive(Debug, Clone)] enum Message { ClientCreated(LuzHandle), Luz(UpdateMessage), Roster(HashMap), Connect, Disconnect, OpenChat(JID), } impl Macaw { fn stream() -> impl Stream { stream::channel(100, |mut output| async { let (luz, recv) = LuzHandle::new( "test@blos.sm".try_into().unwrap(), "slayed".to_string(), "./macaw.db", ) .await .unwrap(); output.send(Message::ClientCreated(luz)).await; let stream = ReceiverStream::new(recv); let stream = stream.map(|message| Message::Luz(message)).map(Ok); stream.forward(output).await; }) } fn subscription(&self) -> Subscription { Subscription::run(Macaw::stream) } fn update(&mut self, message: Message) -> Task { match message { Message::Luz(update_message) => match update_message { UpdateMessage::Error(error) => { tracing::error!("Luz error: {:?}", error); Task::none() } UpdateMessage::Online(online, vec) => { self.connection_status = Some(Presence::Online(online)); let mut roster = HashMap::new(); for contact in vec { roster.insert(contact.user_jid.clone(), contact); } self.roster = roster; Task::none() } UpdateMessage::Offline(offline) => { self.connection_status = Some(Presence::Offline(offline)); Task::none() } UpdateMessage::FullRoster(vec) => { let mut macaw_roster = HashMap::new(); for contact in vec { macaw_roster.insert(contact.user_jid.clone(), contact); } self.roster = macaw_roster; Task::none() } UpdateMessage::RosterUpdate(contact) => { self.roster.insert(contact.user_jid.clone(), contact); Task::none() } UpdateMessage::RosterDelete(jid) => { self.roster.remove(&jid); Task::none() } UpdateMessage::Presence { from, presence } => { self.presences.insert(from, presence); Task::none() } UpdateMessage::Message { to, message } => { if let Some((_chat, message_history)) = self.chats.get_mut(&to) { message_history.push(message); } else { let chat = Chat { correspondent: to.clone(), }; let message_history = vec![message]; self.chats.insert(to, (chat, message_history)); } Task::none() } UpdateMessage::SubscriptionRequest(jid) => { // TODO: subscription requests Task::none() } }, Message::ClientCreated(luz_handle) => { let cloned: LuzHandle = luz_handle.clone(); self.client = Some(cloned); let (send, recv) = oneshot::channel(); Task::perform( async move { luz_handle.send(CommandMessage::GetRoster(send)).await; recv.await }, |result| { let roster = result.unwrap().unwrap(); let mut macaw_roster = HashMap::new(); for contact in roster { macaw_roster.insert(contact.user_jid.clone(), contact); } Message::Roster(macaw_roster) }, ) } Message::Roster(hash_map) => { self.roster = hash_map; Task::none() } Message::Connect => { let client = self.client.clone(); Task::future(async move { client.clone().unwrap().send(CommandMessage::Connect).await; }) .discard() } Message::Disconnect => { let client = self.client.clone(); Task::future(async move { client .clone() .unwrap() .send(CommandMessage::Disconnect(Offline::default())) .await; }) .discard() } Message::OpenChat(jid) => todo!(), } } fn view(&self) -> Element { let mut contacts: Vec> = Vec::new(); for (_, contact) in &self.roster { contacts.push( button(match &contact.user_jid.localpart { Some(u) => u, None => "no username", }) .on_press(Message::OpenChat(contact.user_jid.clone())) .into(), ); } let column = column(contacts); let connection_status = match &self.connection_status { Some(s) => match s { Presence::Online(online) => "connected", Presence::Offline(offline) => "disconnected", }, None => "no account", }; column![ row![ text("test@blos.sm"), text(connection_status), button("connect").on_press(Message::Connect), button("disconnect").on_press(Message::Disconnect) ], text("Buddy List:"), // // column, ] .into() } fn theme(&self) -> Theme { Theme::Dark } }