diff options
| author | 2025-02-26 19:15:52 +0000 | |
|---|---|---|
| committer | 2025-02-26 19:15:52 +0000 | |
| commit | 50e84d47a458420c68ae94dfaa37d901d2a8a4f1 (patch) | |
| tree | c675550d986c15d6eaf6201357e249df55eedb67 /src | |
| parent | 5a2fae397cb0269cdb2b9ce5ded742a78d58cdef (diff) | |
| download | macaw-50e84d47a458420c68ae94dfaa37d901d2a8a4f1.tar.gz macaw-50e84d47a458420c68ae94dfaa37d901d2a8a4f1.tar.bz2 macaw-50e84d47a458420c68ae94dfaa37d901d2a8a4f1.zip | |
feat: chats and messages view, adaptive layout
Diffstat (limited to '')
| -rw-r--r-- | src/main.rs | 255 | 
1 files changed, 207 insertions, 48 deletions
| diff --git a/src/main.rs b/src/main.rs index 1a940fd..e3d3332 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,12 +4,17 @@ use std::fmt::Debug;  use std::ops::{Deref, DerefMut};  use iced::futures::{SinkExt, Stream, StreamExt}; +use iced::widget::button::Status;  use iced::widget::text::{Fragment, IntoFragment};  use iced::widget::{ -    button, center, column, container, mouse_area, opaque, row, stack, text, text_input, Text, +    button, center, column, container, mouse_area, opaque, row, scrollable, stack, text, +    text_input, Column, Text, Toggler,  }; +use iced::Length::Fill;  use iced::{stream, Color, Element, Subscription, Task, Theme}; +use indexmap::{indexmap, IndexMap};  use jid::JID; +use keyring::Entry;  use luz::chat::{Chat, Message as ChatMessage};  use luz::presence::{Offline, Presence};  use luz::CommandMessage; @@ -17,16 +22,36 @@ use luz::{roster::Contact, user::User, LuzHandle, UpdateMessage};  use tokio::sync::{mpsc, oneshot};  use tokio_stream::wrappers::ReceiverStream;  use tracing::info; +use uuid::Uuid; + +pub struct Config { +    auto_connect: bool, +} + +impl Default for Config { +    fn default() -> Self { +        Self { auto_connect: true } +    } +}  pub struct Macaw {      client: Account,      roster: HashMap<JID, Contact>,      users: HashMap<JID, User>,      presences: HashMap<JID, Presence>, -    chats: HashMap<JID, (Chat, Vec<ChatMessage>)>, +    chats: IndexMap<JID, (Chat, IndexMap<Uuid, ChatMessage>)>,      subscription_requests: HashSet<JID>, +    open_chat: Option<OpenChat>, +    new_chat: Option<NewChat>, +} + +pub struct OpenChat { +    jid: JID, +    new_message: String,  } +pub struct NewChat; +  pub struct Creds {      jid: String,      password: String, @@ -50,8 +75,10 @@ impl Macaw {              roster: HashMap::new(),              users: HashMap::new(),              presences: HashMap::new(), -            chats: HashMap::new(), +            chats: IndexMap::new(),              subscription_requests: HashSet::new(), +            open_chat: None, +            new_chat: None,          }      }  } @@ -127,6 +154,11 @@ enum Message {      Connect,      Disconnect,      OpenChat(JID), +    GotChats(Vec<Chat>), +    GotMessageHistory(Chat, IndexMap<Uuid, ChatMessage>), +    CloseChat(JID), +    MessageCompose(String), +    SendMessage(JID, String),  }  #[derive(Debug, Clone)] @@ -197,12 +229,12 @@ impl Macaw {                  }                  UpdateMessage::Message { to, message } => {                      if let Some((_chat, message_history)) = self.chats.get_mut(&to) { -                        message_history.push(message); +                        message_history.insert(message.id, message);                      } else {                          let chat = Chat {                              correspondent: to.clone(),                          }; -                        let message_history = vec![message]; +                        let message_history = indexmap! {message.id => message};                          self.chats.insert(to, (chat, message_history));                      }                      Task::none() @@ -215,21 +247,26 @@ impl Macaw {              // TODO: NEXT              Message::ClientCreated(client) => {                  self.client = Account::LoggedIn(client.clone()); -                let (send, recv) = oneshot::channel(); -                Task::perform( -                    async move { -                        client.client.send(CommandMessage::GetRoster(send)).await; -                        recv.await -                    }, -                    |result| { -                        let roster = result.unwrap().unwrap(); +                let client1 = client.clone(); +                let client2 = client.clone(); +                Task::batch([ +                    Task::perform(async move { client1.client.get_roster().await }, |result| { +                        let roster = result.unwrap();                          let mut macaw_roster = HashMap::new();                          for contact in roster {                              macaw_roster.insert(contact.user_jid.clone(), contact);                          }                          Message::Roster(macaw_roster) -                    }, -                ) +                    }), +                    Task::perform(async move { client2.client.get_chats().await }, |chats| { +                        let chats = chats.unwrap(); +                        // let chats: HashMap<JID, (Chat, IndexMap<Uuid, ChatMessage>)> = chats +                        //     .into_iter() +                        //     .map(|chat| (chat.correspondent.clone(), (chat, IndexMap::new()))) +                        //     .collect(); +                        Message::GotChats(chats) +                    }), +                ])              }              Message::Roster(hash_map) => {                  self.roster = hash_map; @@ -265,7 +302,13 @@ impl Macaw {                      error,                  } => Task::none(),              }, -            Message::OpenChat(jid) => todo!(), +            Message::OpenChat(jid) => { +                self.open_chat = Some(OpenChat { +                    jid, +                    new_message: String::new(), +                }); +                Task::none() +            }              Message::LoginModal(login_modal_message) => match login_modal_message {                  LoginModalMessage::JID(j) => match &mut self.client {                      Account::LoggedIn(_client) => Task::none(), @@ -356,21 +399,100 @@ impl Macaw {                      }                  },              }, +            Message::GotChats(chats) => { +                let mut tasks = Vec::new(); +                let client = match &self.client { +                    Account::LoggedIn(client) => client, +                    Account::LoggedOut { +                        jid, +                        password, +                        error, +                    } => panic!("no client"), +                }; +                for chat in chats { +                    let client = client.clone(); +                    let correspondent = chat.correspondent.clone(); +                    tasks.push(Task::perform( +                        async move { (chat, client.get_messages(correspondent).await) }, +                        |result| { +                            let messages: IndexMap<Uuid, ChatMessage> = result +                                .1 +                                .unwrap() +                                .into_iter() +                                .map(|message| (message.id.clone(), message)) +                                .collect(); +                            Message::GotMessageHistory(result.0, messages) +                        }, +                    )) +                } +                Task::batch(tasks) +                // .then(|chats| { +                //     let tasks = Vec::new(); +                //     for key in chats.keys() { +                //         let client = client.client.clone(); +                //         tasks.push(Task::future(async { +                //             client.get_messages(key.clone()).await; +                //         })); +                //     } +                //     Task::batch(tasks) +                // }), +            } +            Message::GotMessageHistory(chat, message_history) => { +                self.chats +                    .insert(chat.correspondent.clone(), (chat, message_history)); +                Task::none() +            } +            Message::CloseChat(jid) => { +                self.open_chat = None; +                Task::none() +            } +            Message::MessageCompose(m) => { +                if let Some(open_chat) = &mut self.open_chat { +                    open_chat.new_message = m; +                } +                Task::none() +            } +            Message::SendMessage(jid, body) => { +                let client = match &self.client { +                    Account::LoggedIn(client) => client.clone(), +                    Account::LoggedOut { +                        jid, +                        password, +                        error, +                    } => todo!(), +                }; +                Task::future( +                    async move { client.send_message(jid, luz::chat::Body { body }).await }, +                ) +                .discard() +            }          }      }      fn view(&self) -> Element<Message> { -        let ui = { -            let mut contacts: Vec<Element<Message>> = Vec::new(); -            for (_, contact) in &self.roster { -                let jid: Cow<'_, str> = (&contact.user_jid).into(); -                contacts.push( -                    button(text(jid)) -                        .on_press(Message::OpenChat(contact.user_jid.clone())) -                        .into(), -                ); +        let mut ui: Element<Message> = { +            let mut chats_list: Column<Message> = column![]; +            for (jid, chat) in &self.chats { +                let cow_jid: Cow<'_, str> = (jid).into(); +                let mut toggler: Toggler<Message> = iced::widget::toggler(false); +                if let Some(open_chat) = &self.open_chat { +                    if open_chat.jid == *jid { +                        toggler = iced::widget::toggler(true) +                    } +                } +                let toggler = toggler +                    .on_toggle(|open| { +                        if open { +                            Message::OpenChat(jid.clone()) +                        } else { +                            Message::CloseChat(jid.clone()) +                        } +                    }) +                    .label(cow_jid); +                chats_list = chats_list.push(toggler);              } -            let column = column(contacts); +            let chats_list = scrollable(chats_list).height(Fill); +              let connection_status = match &self.client {                  Account::LoggedIn(client) => match &client.connection_status {                      Presence::Online(_online) => "online", @@ -382,13 +504,6 @@ impl Macaw {                      error,                  } => "disconnected",              }; -            // match &self.client.as_ref().map(|client| &client.connection_status) { -            //     Some(s) => match s { -            //         Presence::Online(online) => "connected", -            //         Presence::Offline(offline) => "disconnected", -            //     }, -            //     None => "no account", -            // };              let client_jid: Cow<'_, str> = match &self.client {                  Account::LoggedIn(client) => (&client.jid).into(),                  Account::LoggedOut { @@ -398,25 +513,69 @@ impl Macaw {                  } => Cow::from("no account"),                  // map(|client| (&client.jid).into());              }; -            column![ -                row![ -                    text(client_jid), -                    text(connection_status), -                    button("connect").on_press(Message::Connect), -                    button("disconnect").on_press(Message::Disconnect) -                ], -                text("Buddy List:"), -                // -                // -                column, -            ] -        }; +            let account_view = row![ +                text(client_jid), +                text(connection_status), +                button("connect").on_press(Message::Connect), +                button("disconnect").on_press(Message::Disconnect) +            ]; + +            let sidebar = column![chats_list, account_view].height(Fill); + +            let message_view; +            if let Some(open_chat) = &self.open_chat { +                let (chat, messages) = self.chats.get(&open_chat.jid).unwrap(); +                let mut messages_view = column![]; +                for (_id, message) in messages { +                    let from: Cow<'_, str> = (&message.from).into(); +                    let message: Column<Message> = +                        column![text(from).size(12), text(&message.body.body)].into(); +                    messages_view = messages_view.push(message); +                } +                let message_send_input = row![ +                    text_input("new message", &open_chat.new_message) +                        .on_input(Message::MessageCompose), +                    button("send").on_press(Message::SendMessage( +                        chat.correspondent.clone(), +                        open_chat.new_message.clone() +                    )) +                ]; +                message_view = column![ +                    scrollable(messages_view) +                        .height(Fill) +                        .width(Fill) +                        .anchor_bottom(), +                    message_send_input +                ]; +            } else { +                message_view = column![]; +            } +            row![sidebar, message_view.width(Fill)] + +            // old + +            // let mut contacts: Vec<Element<Message>> = Vec::new(); +            // for (_, contact) in &self.roster { +            //     let jid: Cow<'_, str> = (&contact.user_jid).into(); +            //     contacts.push( +            //         button(text(jid)) +            //             .on_press(Message::OpenChat(contact.user_jid.clone())) +            //             .into(), +            //     ); +            // } +        } +        .into(); + +        if let Some(new_chat) = &self.new_chat { +            ui = modal(ui, text("new chat")); +        }          // temporarily center to fill space -        let ui = center(ui).into(); +        // let ui = center(ui).into(); +        let ui = container(ui).center_x(Fill).center_y(Fill);          match &self.client { -            Account::LoggedIn(_client) => ui, +            Account::LoggedIn(_client) => ui.into(),              Account::LoggedOut {                  jid,                  password, | 
