aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs291
1 files changed, 214 insertions, 77 deletions
diff --git a/src/main.rs b/src/main.rs
index 27314ae..ed6ce1b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,10 @@ use std::str::FromStr;
use std::sync::Arc;
use iced::futures::{SinkExt, Stream, StreamExt};
+use iced::theme::palette::{
+ Background, Danger, Extended, Pair, Primary, Secondary, Success, Warning,
+};
+use iced::theme::{Custom, Palette};
use iced::widget::button::Status;
use iced::widget::text::{Fragment, IntoFragment};
use iced::widget::{
@@ -14,15 +18,17 @@ use iced::widget::{
text_input, Column, Text, Toggler,
};
use iced::Length::Fill;
-use iced::{stream, Color, Element, Subscription, Task, Theme};
+use iced::{color, stream, Color, Element, Subscription, Task, Theme};
use indexmap::{indexmap, IndexMap};
use jid::JID;
use keyring::Entry;
use login_modal::{Creds, LoginModal};
use luz::chat::{Chat, Message as ChatMessage};
+use luz::error::CommandError;
use luz::presence::{Offline, Presence};
use luz::CommandMessage;
use luz::{roster::Contact, user::User, LuzHandle, UpdateMessage};
+use message_view::MessageView;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio::sync::{mpsc, oneshot};
@@ -31,6 +37,7 @@ use tracing::{error, info};
use uuid::Uuid;
mod login_modal;
+mod message_view;
#[derive(Serialize, Deserialize, Clone)]
pub struct Config {
@@ -55,17 +62,12 @@ pub struct Macaw {
roster: HashMap<JID, Contact>,
users: HashMap<JID, User>,
presences: HashMap<JID, Presence>,
- chats: IndexMap<JID, (Chat, IndexMap<Uuid, ChatMessage>)>,
+ chats: IndexMap<JID, (Chat, ChatMessage)>,
subscription_requests: HashSet<JID>,
- open_chat: Option<OpenChat>,
+ open_chat: Option<MessageView>,
new_chat: Option<NewChat>,
}
-pub struct OpenChat {
- jid: JID,
- new_message: String,
-}
-
pub struct NewChat;
impl Macaw {
@@ -157,7 +159,7 @@ async fn luz(jid: &JID, creds: &Creds, cfg: &Config) -> (LuzHandle, mpsc::Receiv
async fn main() -> iced::Result {
tracing_subscriber::fmt::init();
- let cfg: Config = confy::load("macaw", None).unwrap();
+ let cfg: Config = confy::load("macaw", None).unwrap_or_default();
let entry = Entry::new("macaw", "macaw");
let mut client_creation_error: Option<Error> = None;
let mut creds: Option<Creds> = None;
@@ -213,21 +215,23 @@ async fn main() -> iced::Result {
let luz_handle2 = luz_handle.clone();
if cfg.auto_connect {
Task::batch([
- Task::perform(async move { luz_handle1.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 { luz_handle2.get_chats().await }, |chats| {
- let chats = chats.unwrap();
- info!("got chats: {:?}", chats);
- Message::GotChats(chats)
- }),
+ Task::batch([
+ Task::perform(async move { luz_handle1.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 { luz_handle2.get_chats().await }, |chats| {
+ let chats = chats.unwrap();
+ info!("got chats: {:?}", chats);
+ Message::GotChats(chats)
+ }),
+ ])
+ .chain(Task::done(Message::Connect)),
Task::stream(stream),
- Task::done(Message::Connect),
])
} else {
Task::batch([
@@ -248,20 +252,22 @@ async fn main() -> iced::Result {
])
}
};
- iced::application("Macaw", Macaw::update, Macaw::view).run_with(|| {
- (
- Macaw::new(
- Some(Client {
- client: luz_handle,
- // TODO:
- jid,
- connection_status: Presence::Offline(Offline::default()),
- }),
- cfg,
- ),
- task,
- )
- })
+ iced::application("Macaw", Macaw::update, Macaw::view)
+ .theme(Macaw::theme)
+ .run_with(|| {
+ (
+ Macaw::new(
+ Some(Client {
+ client: luz_handle,
+ // TODO:
+ jid,
+ connection_status: Presence::Offline(Offline::default()),
+ }),
+ cfg,
+ ),
+ task,
+ )
+ })
} else {
if let Some(e) = client_creation_error {
iced::application("Macaw", Macaw::update, Macaw::view)
@@ -288,6 +294,7 @@ pub enum Message {
MessageCompose(String),
SendMessage(JID, String),
Error(Error),
+ MessageView(message_view::Message),
}
#[derive(Debug, Error, Clone)]
@@ -298,6 +305,8 @@ pub enum Error {
CredentialsSave(CredentialsSaveError),
#[error("failed to load credentials: {0}")]
CredentialsLoad(CredentialsLoadError),
+ #[error("failed to retreive messages for chat {0}")]
+ MessageHistory(JID, CommandError<luz::error::DatabaseError>),
}
#[derive(Debug, Error, Clone)]
@@ -381,14 +390,22 @@ impl Macaw {
Task::none()
}
UpdateMessage::Message { to, message } => {
- if let Some((_chat, message_history)) = self.chats.get_mut(&to) {
- message_history.insert(message.id, message);
+ if let Some((chat_jid, (chat, old_message))) =
+ self.chats.shift_remove_entry(&to)
+ {
+ self.chats
+ .insert_before(0, chat_jid, (chat, message.clone()));
+ if let Some(open_chat) = &mut self.open_chat {
+ if open_chat.jid == to {
+ open_chat.update(message_view::Message::Message(message));
+ }
+ }
} else {
let chat = Chat {
correspondent: to.clone(),
};
- let message_history = indexmap! {message.id => message};
- self.chats.insert(to, (chat, message_history));
+ let message_history = indexmap! {message.id => message.clone()};
+ self.chats.insert_before(0, to, (chat, message));
}
Task::none()
}
@@ -421,8 +438,8 @@ impl Macaw {
info!("got chats: {:?}", chats);
Message::GotChats(chats)
}),
- Task::done(Message::Connect),
])
+ .chain(Task::done(Message::Connect))
} else {
Task::batch([
Task::perform(async move { client1.client.get_roster().await }, |result| {
@@ -472,11 +489,23 @@ impl Macaw {
Account::LoggedOut(login_modal) => Task::none(),
},
Message::OpenChat(jid) => {
- self.open_chat = Some(OpenChat {
- jid,
- new_message: String::new(),
- });
- Task::none()
+ self.open_chat = Some(MessageView::new(jid.clone()));
+ let jid1 = jid.clone();
+ match &self.client {
+ Account::LoggedIn(client) => {
+ let client = client.clone();
+ Task::perform(
+ async move { client.get_messages(jid1).await },
+ move |result| match result {
+ Ok(h) => {
+ Message::MessageView(message_view::Message::MessageHistory(h))
+ }
+ Err(e) => Message::Error(Error::MessageHistory(jid.clone(), e)),
+ },
+ )
+ }
+ Account::LoggedOut(login_modal) => Task::none(),
+ }
}
Message::LoginModal(login_modal_message) => match &mut self.client {
Account::LoggedIn(_client) => Task::none(),
@@ -567,6 +596,7 @@ impl Macaw {
let client = client.clone();
let correspondent = chat.correspondent.clone();
tasks.push(Task::perform(
+ // TODO: don't get the entire message history LOL
async move { (chat, client.get_messages(correspondent).await) },
|result| {
let messages: IndexMap<Uuid, ChatMessage> = result
@@ -591,9 +621,12 @@ impl Macaw {
// Task::batch(tasks)
// }),
}
- Message::GotMessageHistory(chat, message_history) => {
- self.chats
- .insert(chat.correspondent.clone(), (chat, message_history));
+ Message::GotMessageHistory(chat, mut message_history) => {
+ // TODO: don't get the entire message history LOL
+ if let Some((_id, message)) = message_history.pop() {
+ self.chats
+ .insert(chat.correspondent.clone(), (chat, message));
+ }
Task::none()
}
Message::CloseChat(jid) => {
@@ -619,7 +652,23 @@ impl Macaw {
)
.discard()
}
- Message::Error(error) => todo!("error notification toasts, logging?"),
+ Message::Error(error) => {
+ error!("{}", error);
+ Task::none()
+ }
+ Message::MessageView(message) => {
+ if let Some(message_view) = &mut self.open_chat {
+ 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.jid.clone(), m))
+ }
+ }
+ } else {
+ Task::none()
+ }
+ }
}
}
@@ -670,34 +719,12 @@ impl Macaw {
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
- ];
+ message_view = open_chat.view().map(Message::MessageView)
} else {
- message_view = column![];
+ message_view = column![].into();
}
- row![sidebar, message_view.width(Fill)]
+ row![sidebar, container(message_view).width(Fill)]
// old
@@ -731,7 +758,117 @@ impl Macaw {
}
fn theme(&self) -> Theme {
- Theme::Dark
+ let extended = Extended {
+ background: Background {
+ base: Pair {
+ color: color!(0x392c25),
+ text: color!(0xdcdcdc),
+ },
+ weakest: Pair {
+ color: color!(0xdcdcdc),
+ text: color!(0x392c25),
+ },
+ weak: Pair {
+ color: color!(0xdcdcdc),
+ text: color!(0x392c25),
+ },
+ strong: Pair {
+ color: color!(0x364b3b),
+ text: color!(0xdcdcdc),
+ },
+ strongest: Pair {
+ color: color!(0x364b3b),
+ text: color!(0xdcdcdc),
+ },
+ },
+ primary: Primary {
+ base: Pair {
+ color: color!(0x2b33b4),
+ text: color!(0xdcdcdc),
+ },
+ weak: Pair {
+ color: color!(0x4D4A5E),
+ text: color!(0xdcdcdc),
+ },
+ strong: Pair {
+ color: color!(0x2b33b4),
+ text: color!(0xdcdcdc),
+ },
+ },
+ secondary: Secondary {
+ base: Pair {
+ color: color!(0xffce07),
+ text: color!(0x000000),
+ },
+ weak: Pair {
+ color: color!(0xffce07),
+ text: color!(0x000000),
+ },
+ strong: Pair {
+ color: color!(0xffce07),
+ text: color!(0x000000),
+ },
+ },
+ success: Success {
+ base: Pair {
+ color: color!(0x14802E),
+ text: color!(0xdcdcdc),
+ },
+ weak: Pair {
+ color: color!(0x14802E),
+ text: color!(0xdcdcdc),
+ },
+ strong: Pair {
+ color: color!(0x14802E),
+ 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),
+ text: color!(0xdcdcdc),
+ },
+ weak: Pair {
+ color: color!(0xC1173C),
+ text: color!(0xdcdcdc),
+ },
+ strong: Pair {
+ color: color!(0xC1173C),
+ text: color!(0xdcdcdc),
+ },
+ },
+ is_dark: true,
+ };
+ Theme::Custom(Arc::new(Custom::with_fn(
+ "macaw".to_string(),
+ Palette::DARK,
+ |_| extended,
+ )))
+ // Theme::Custom(Arc::new(Custom::new(
+ // "macaw".to_string(),
+ // Palette {
+ // background: color!(0x392c25),
+ // text: color!(0xdcdcdc),
+ // primary: color!(0x2b33b4),
+ // success: color!(0x14802e),
+ // warning: color!(0xffce07),
+ // danger: color!(0xc1173c),
+ // },
+ // )))
}
}