diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/login_modal.rs | 147 | ||||
| -rw-r--r-- | src/main.rs | 293 | 
2 files changed, 216 insertions, 224 deletions
| diff --git a/src/login_modal.rs b/src/login_modal.rs new file mode 100644 index 0000000..f4e556e --- /dev/null +++ b/src/login_modal.rs @@ -0,0 +1,147 @@ +use iced::{ +    futures::StreamExt, +    widget::{button, checkbox, column, container, text, text_input}, +    Element, Task, +}; +use jid::JID; +use luz::{ +    presence::{Offline, Presence}, +    LuzHandle, +}; +use tokio_stream::wrappers::ReceiverStream; +use tracing::info; + +use crate::Client; + +#[derive(Default)] +pub struct LoginModal { +    jid: String, +    password: String, +    remember_me: bool, +    error: Option<Error>, +} + +#[derive(Debug, Clone)] +pub enum Message { +    JID(String), +    Password(String), +    RememberMe, +    Submit, +    Error(Error), +} + +#[derive(Debug, Clone)] +pub enum Error { +    InvalidJID(String), +    DatabaseConnection, +} + +pub enum Action { +    None, +    ClientCreated(Task<crate::Message>), +} + +impl LoginModal { +    pub fn update(&mut self, message: Message) -> Action { +        match message { +            Message::JID(j) => { +                self.jid = j; +                Action::None +            } +            Message::Password(p) => { +                self.password = p; +                Action::None +            } +            Message::RememberMe => { +                self.remember_me = !self.remember_me; +                Action::None +            } +            Message::Submit => { +                info!("submitting login"); +                let jid_str = self.jid.clone(); +                let password = self.password.clone(); +                Action::ClientCreated( +                    Task::future(async move { +                        let jid: Result<JID, _> = jid_str.parse(); +                        match jid { +                            Ok(j) => { +                                let result = +                                    LuzHandle::new(j.clone(), password.to_string(), "macaw.db") +                                        .await; +                                match result { +                                    Ok((luz_handle, receiver)) => { +                                        let stream = ReceiverStream::new(receiver); +                                        let stream = +                                            stream.map(|message| crate::Message::Luz(message)); +                                        vec![ +                                            Task::done(crate::Message::ClientCreated(Client { +                                                client: luz_handle, +                                                jid: j, +                                                connection_status: Presence::Offline( +                                                    Offline::default(), +                                                ), +                                            })), +                                            Task::stream(stream), +                                        ] +                                    } +                                    Err(_e) => { +                                        tracing::error!("error (database probably)"); +                                        return vec![Task::done(crate::Message::LoginModal( +                                            Message::Error(Error::DatabaseConnection), +                                        ))]; +                                    } +                                } +                            } +                            Err(_) => { +                                tracing::error!("parsing jid"); +                                return vec![Task::done(crate::Message::LoginModal( +                                    Message::Error(Error::InvalidJID(jid_str.to_string())), +                                ))]; +                            } +                        } +                    }) +                    .then(|tasks| Task::batch(tasks)), +                ) +            } +            Message::Error(error) => { +                self.error = Some(error); +                Action::None +            } +        } +    } + +    pub fn view(&self) -> Element<Message> { +        container( +            column![ +                text("Log In").size(24), +                column![ +                    column![ +                        text("JID").size(12), +                        text_input("berry@macaw.chat", &self.jid) +                            .on_input(|j| Message::JID(j)) +                            .on_submit(Message::Submit) +                            .padding(5), +                    ] +                    .spacing(5), +                    column![ +                        text("Password").size(12), +                        text_input("", &self.password) +                            .on_input(|p| Message::Password(p)) +                            .on_submit(Message::Submit) +                            .secure(true) +                            .padding(5), +                    ] +                    .spacing(5), +                    checkbox("remember me", self.remember_me).on_toggle(|_| Message::RememberMe), +                    button(text("Submit")).on_press(Message::Submit), +                ] +                .spacing(10) +            ] +            .spacing(20), +        ) +        .width(300) +        .padding(10) +        .style(container::rounded_box) +        .into() +    } +} diff --git a/src/main.rs b/src/main.rs index e3d3332..d583293 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ 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, scrollable, stack, text, +    button, center, checkbox, column, container, mouse_area, opaque, row, scrollable, stack, text,      text_input, Column, Text, Toggler,  };  use iced::Length::Fill; @@ -15,15 +15,20 @@ use iced::{stream, Color, Element, Subscription, Task, Theme};  use indexmap::{indexmap, IndexMap};  use jid::JID;  use keyring::Entry; +use login_modal::LoginModal;  use luz::chat::{Chat, Message as ChatMessage};  use luz::presence::{Offline, Presence};  use luz::CommandMessage;  use luz::{roster::Contact, user::User, LuzHandle, UpdateMessage}; +use serde::{Deserialize, Serialize};  use tokio::sync::{mpsc, oneshot};  use tokio_stream::wrappers::ReceiverStream; -use tracing::info; +use tracing::{error, info};  use uuid::Uuid; +mod login_modal; + +#[derive(Serialize, Deserialize)]  pub struct Config {      auto_connect: bool,  } @@ -36,6 +41,7 @@ impl Default for Config {  pub struct Macaw {      client: Account, +    config: Config,      roster: HashMap<JID, Contact>,      users: HashMap<JID, User>,      presences: HashMap<JID, Presence>, @@ -58,20 +64,17 @@ pub struct Creds {  }  impl Macaw { -    pub fn new(client: Option<Client>) -> Self { +    pub fn new(client: Option<Client>, config: Config) -> Self {          let account;          if let Some(client) = client {              account = Account::LoggedIn(client);          } else { -            account = Account::LoggedOut { -                jid: "".to_string(), -                password: "".to_string(), -                error: None, -            }; +            account = Account::LoggedOut(LoginModal::default());          }          Self {              client: account, +            config,              roster: HashMap::new(),              users: HashMap::new(),              presences: HashMap::new(), @@ -85,17 +88,7 @@ impl Macaw {  pub enum Account {      LoggedIn(Client), -    LoggedOut { -        jid: String, -        password: String, -        error: Option<Error>, -    }, -} - -#[derive(Debug, Clone)] -pub enum Error { -    InvalidJID(String), -    DatabaseConnection, +    LoggedOut(LoginModal),  }  #[derive(Clone, Debug)] @@ -122,6 +115,7 @@ impl Deref for Client {  fn main() -> iced::Result {      tracing_subscriber::fmt::init(); +    let cfg = confy::load("macaw", None).unwrap();      let client: Option<(JID, LuzHandle, mpsc::Receiver<UpdateMessage>)> = None;      if let Some((jid, luz_handle, update_recv)) = client { @@ -129,25 +123,28 @@ fn main() -> iced::Result {          let stream = stream.map(|message| Message::Luz(message));          iced::application("Macaw", Macaw::update, Macaw::view).run_with(|| {              ( -                Macaw::new(Some(Client { -                    client: luz_handle, -                    // TODO: -                    jid, -                    connection_status: Presence::Offline(Offline::default()), -                })), +                Macaw::new( +                    Some(Client { +                        client: luz_handle, +                        // TODO: +                        jid, +                        connection_status: Presence::Offline(Offline::default()), +                    }), +                    cfg, +                ),                  // TODO: autoconnect config                  Task::stream(stream),              )          })      } else {          iced::application("Macaw", Macaw::update, Macaw::view) -            .run_with(|| (Macaw::new(None), Task::none())) +            .run_with(|| (Macaw::new(None, cfg), Task::none()))      }  }  #[derive(Debug, Clone)] -enum Message { -    LoginModal(LoginModalMessage), +pub enum Message { +    LoginModal(login_modal::Message),      ClientCreated(Client),      Luz(UpdateMessage),      Roster(HashMap<JID, Contact>), @@ -161,14 +158,6 @@ enum Message {      SendMessage(JID, String),  } -#[derive(Debug, Clone)] -enum LoginModalMessage { -    JID(String), -    Password(String), -    Submit, -    Error(Error), -} -  impl Macaw {      fn update(&mut self, message: Message) -> Task<Message> {          match message { @@ -187,11 +176,7 @@ impl Macaw {                          self.roster = roster;                          Task::none()                      } -                    Account::LoggedOut { -                        jid, -                        password, -                        error, -                    } => Task::none(), +                    Account::LoggedOut(login_modal) => Task::none(),                  },                  UpdateMessage::Offline(offline) => {                      // TODO: update all contacts' presences to unknown (offline) @@ -200,11 +185,7 @@ impl Macaw {                              client.connection_status = Presence::Offline(offline);                              Task::none()                          } -                        Account::LoggedOut { -                            jid, -                            password, -                            error, -                        } => Task::none(), +                        Account::LoggedOut(login_modal) => Task::none(),                      }                  }                  UpdateMessage::FullRoster(vec) => { @@ -280,11 +261,7 @@ impl Macaw {                      })                      .discard()                  } -                Account::LoggedOut { -                    jid, -                    password, -                    error, -                } => Task::none(), +                Account::LoggedOut(login_modal) => Task::none(),              },              Message::Disconnect => match &self.client {                  Account::LoggedIn(client) => { @@ -296,11 +273,7 @@ impl Macaw {                      })                      .discard()                  } -                Account::LoggedOut { -                    jid, -                    password, -                    error, -                } => Task::none(), +                Account::LoggedOut(login_modal) => Task::none(),              },              Message::OpenChat(jid) => {                  self.open_chat = Some(OpenChat { @@ -309,105 +282,25 @@ impl Macaw {                  });                  Task::none()              } -            Message::LoginModal(login_modal_message) => match login_modal_message { -                LoginModalMessage::JID(j) => match &mut self.client { -                    Account::LoggedIn(_client) => Task::none(), -                    Account::LoggedOut { -                        jid, -                        password, -                        error, -                    } => { -                        *jid = j; -                        Task::none() +            Message::LoginModal(login_modal_message) => match &mut self.client { +                Account::LoggedIn(_client) => Task::none(), +                Account::LoggedOut(login_modal) => { +                    let action = login_modal.update(login_modal_message); +                    match action { +                        login_modal::Action::None => Task::none(), +                        login_modal::Action::ClientCreated(task) => task,                      } -                }, -                LoginModalMessage::Password(p) => match &mut self.client { -                    Account::LoggedIn(_client) => Task::none(), -                    Account::LoggedOut { -                        jid, -                        password, -                        error, -                    } => { -                        *password = p; -                        Task::none() -                    } -                }, -                LoginModalMessage::Submit => match &self.client { -                    Account::LoggedIn(_client) => Task::none(), -                    Account::LoggedOut { -                        jid: jid_str, -                        password, -                        error, -                    } => { -                        info!("submitting login"); -                        let jid_str = jid_str.clone(); -                        let password = password.clone(); -                        Task::future(async move { -                            let jid: Result<JID, _> = jid_str.parse(); -                            match jid { -                                Ok(j) => { -                                    let result = -                                        LuzHandle::new(j.clone(), password.to_string(), "macaw.db") -                                            .await; -                                    match result { -                                        Ok((luz_handle, receiver)) => { -                                            let stream = ReceiverStream::new(receiver); -                                            let stream = -                                                stream.map(|message| Message::Luz(message)); -                                            vec![ -                                                Task::done(Message::ClientCreated(Client { -                                                    client: luz_handle, -                                                    jid: j, -                                                    connection_status: Presence::Offline( -                                                        Offline::default(), -                                                    ), -                                                })), -                                                Task::stream(stream), -                                            ] -                                        } -                                        Err(e) => { -                                            tracing::error!("error (database probably)"); -                                            return vec![Task::done(Message::LoginModal( -                                                LoginModalMessage::Error(Error::DatabaseConnection), -                                            ))]; -                                        } -                                    } -                                } -                                Err(_) => { -                                    tracing::error!("parsing jid"); -                                    return vec![Task::done(Message::LoginModal( -                                        LoginModalMessage::Error(Error::InvalidJID( -                                            jid_str.to_string(), -                                        )), -                                    ))]; -                                } -                            } -                        }) -                        .then(|tasks| Task::batch(tasks)) -                    } -                }, -                LoginModalMessage::Error(e) => match &mut self.client { -                    Account::LoggedIn(_client) => Task::none(), -                    Account::LoggedOut { -                        jid, -                        password, -                        error, -                    } => { -                        tracing::error!("luz::new: {:?}", e); -                        *error = Some(e); -                        Task::none() -                    } -                }, +                }              },              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"), +                    Account::LoggedOut(_) => { +                        // TODO: error into event tracing subscriber +                        error!("no client, cannot retreive chat history for chats"); +                        return Task::none(); +                    }                  };                  for chat in chats {                      let client = client.clone(); @@ -455,11 +348,10 @@ impl Macaw {              Message::SendMessage(jid, body) => {                  let client = match &self.client {                      Account::LoggedIn(client) => client.clone(), -                    Account::LoggedOut { -                        jid, -                        password, -                        error, -                    } => todo!(), +                    Account::LoggedOut(_) => { +                        error!("cannot send message when no client set up"); +                        return Task::none(); +                    }                  };                  Task::future(                      async move { client.send_message(jid, luz::chat::Body { body }).await }, @@ -498,19 +390,11 @@ impl Macaw {                      Presence::Online(_online) => "online",                      Presence::Offline(_offline) => "disconnected",                  }, -                Account::LoggedOut { -                    jid: _, -                    password: _, -                    error, -                } => "disconnected", +                Account::LoggedOut(_) => "disconnected",              };              let client_jid: Cow<'_, str> = match &self.client {                  Account::LoggedIn(client) => (&client.jid).into(), -                Account::LoggedOut { -                    jid: _, -                    password: _, -                    error, -                } => Cow::from("no account"), +                Account::LoggedOut(_) => Cow::from("no account"),                  // map(|client| (&client.jid).into());              };              let account_view = row![ @@ -568,7 +452,8 @@ impl Macaw {          .into();          if let Some(new_chat) = &self.new_chat { -            ui = modal(ui, text("new chat")); +            // TODO: close new chat window +            ui = modal(ui, text("new chat"), None);          }          // temporarily center to fill space          // let ui = center(ui).into(); @@ -576,47 +461,9 @@ impl Macaw {          match &self.client {              Account::LoggedIn(_client) => ui.into(), -            Account::LoggedOut { -                jid, -                password, -                error, -            } => { -                let signup = container( -                    column![ -                        text("Log In").size(24), -                        column![ -                            column![ -                                text("JID").size(12), -                                text_input("berry@macaw.chat", &jid) -                                    .on_input(|j| Message::LoginModal(LoginModalMessage::JID(j))) -                                    .on_submit(Message::LoginModal(LoginModalMessage::Submit)) -                                    .padding(5), -                            ] -                            .spacing(5), -                            column![ -                                text("Password").size(12), -                                text_input("", &password) -                                    .on_input(|p| Message::LoginModal(LoginModalMessage::Password( -                                        p -                                    ))) -                                    .on_submit(Message::LoginModal(LoginModalMessage::Submit)) -                                    .secure(true) -                                    .padding(5), -                            ] -                            .spacing(5), -                            button(text("Submit")) -                                .on_press(Message::LoginModal(LoginModalMessage::Submit)), -                        ] -                        .spacing(10) -                    ] -                    .spacing(20), -                ) -                .width(300) -                .padding(10) -                .style(container::rounded_box); - -                // signup.into() -                modal(ui, signup) +            Account::LoggedOut(login_modal) => { +                let signup = login_modal.view().map(Message::LoginModal); +                modal(ui, signup, None)              }          }      } @@ -629,27 +476,25 @@ impl Macaw {  fn modal<'a, Message>(      base: impl Into<Element<'a, Message>>,      content: impl Into<Element<'a, Message>>, -    // on_blur: Message, +    on_blur: Option<Message>,  ) -> Element<'a, Message>  where      Message: Clone + 'a,  { -    stack![ -        base.into(), -        opaque( -            mouse_area(center(opaque(content)).style(|_theme| { -                container::Style { -                    background: Some( -                        Color { -                            a: 0.8, -                            ..Color::BLACK -                        } -                        .into(), -                    ), -                    ..container::Style::default() +    let mut mouse_area = mouse_area(center(opaque(content)).style(|_theme| { +        container::Style { +            background: Some( +                Color { +                    a: 0.8, +                    ..Color::BLACK                  } -            })) // .on_press(on_blur) -        ) -    ] -    .into() +                .into(), +            ), +            ..container::Style::default() +        } +    })); // .on_press(on_blur) +    if let Some(on_blur) = on_blur { +        mouse_area = mouse_area.on_press(on_blur) +    } +    stack![base.into(), opaque(mouse_area)].into()  } | 
