diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 1062 |
1 files changed, 9 insertions, 1053 deletions
diff --git a/src/main.rs b/src/main.rs index 37551c8..3db4c25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,1057 +1,13 @@ -use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; -use std::ops::{Deref, DerefMut}; -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; +// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden> +// +// SPDX-License-Identifier: AGPL-3.0-or-later -use chrono::{Local, Utc}; -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::{Custom, Palette}; -use iced::widget::button::Status; -use iced::widget::text::{Fragment, IntoFragment, Wrapping}; -use iced::widget::{ - button, center, checkbox, column, container, horizontal_space, mouse_area, opaque, row, - scrollable, stack, text, text_input, toggler, Column, Text, Toggler, -}; -use iced::Length::{self, Fill, Shrink}; -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, PresenceType}; -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}; -use tokio_stream::wrappers::ReceiverStream; -use tracing::{error, info}; -use uuid::Uuid; +use leptos::prelude::*; +use macaw_web::App; -mod login_modal; -mod message_view; +fn main() { + tracing_wasm::set_as_global_default(); + console_error_panic_hook::set_once(); -#[derive(Serialize, Deserialize, Clone)] -pub struct Config { - auto_connect: bool, - storage_dir: Option<String>, - dburl: Option<String>, - message_view_config: message_view::Config, -} - -impl Default for Config { - fn default() -> Self { - Self { - auto_connect: true, - storage_dir: None, - dburl: None, - message_view_config: message_view::Config::default(), - } - } -} - -pub struct Macaw { - client: Account, - config: Config, - roster: HashMap<JID, Contact>, - users: HashMap<JID, User>, - presences: HashMap<JID, Presence>, - chats: IndexMap<JID, (Chat, Option<ChatMessage>)>, - subscription_requests: HashSet<JID>, - open_chat: Option<MessageView>, - new_chat: Option<NewChat>, -} - -pub struct NewChat; - -impl Macaw { - pub fn new(client: Option<Client>, config: Config) -> Self { - let account; - if let Some(client) = client { - account = Account::LoggedIn(client); - } else { - account = Account::LoggedOut(LoginModal::default()); - } - - Self { - client: account, - config, - roster: HashMap::new(), - users: HashMap::new(), - presences: HashMap::new(), - chats: IndexMap::new(), - subscription_requests: HashSet::new(), - open_chat: None, - new_chat: None, - } - } -} - -pub enum Account { - LoggedIn(Client), - LoggedOut(LoginModal), -} - -impl Account { - pub fn is_connected(&self) -> bool { - match self { - Account::LoggedIn(client) => client.connection_state.is_connected(), - Account::LoggedOut(login_modal) => false, - } - } - - pub fn connection_status(&self) -> String { - match self { - Account::LoggedIn(client) => match client.connection_state { - ConnectionState::Online => "online".to_string(), - ConnectionState::Connecting => "connecting".to_string(), - ConnectionState::Offline => "offline".to_string(), - }, - Account::LoggedOut(login_modal) => "no account".to_string(), - } - } -} - -#[derive(Clone, Debug)] -pub struct Client { - client: LuzHandle, - jid: JID, - status: Presence, - connection_state: ConnectionState, -} - -impl Client { - pub fn is_connected(&self) -> bool { - self.connection_state.is_connected() - } -} - -#[derive(Clone, Debug)] -pub enum ConnectionState { - Online, - Connecting, - Offline, -} - -impl ConnectionState { - pub fn is_connected(&self) -> bool { - match self { - ConnectionState::Online => true, - ConnectionState::Connecting => false, - ConnectionState::Offline => false, - } - } -} - -impl DerefMut for Client { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.client - } -} - -impl Deref for Client { - type Target = LuzHandle; - - fn deref(&self) -> &Self::Target { - &self.client - } -} - -async fn luz(jid: &JID, creds: &Creds, cfg: &Config) -> (LuzHandle, mpsc::Receiver<UpdateMessage>) { - let luz; - if let Some(ref dburl) = cfg.dburl { - // TODO: have some sort of crash popup for this stuff - let db_path = dburl.strip_prefix("sqlite://").unwrap_or(&dburl); - let db_path = PathBuf::from_str(db_path).expect("invalid database path"); - let db = luz::db::Db::create_connect_and_migrate(db_path) - .await - .unwrap(); - luz = LuzHandle::new(jid.clone(), creds.password.to_string(), db); - } else if let Some(ref dir) = cfg.storage_dir { - let mut data_dir = PathBuf::from_str(&dir).expect("invalid storage directory path"); - data_dir.push(creds.jid.clone()); - data_dir.push(creds.jid.clone()); - data_dir.set_extension("db"); - let db = luz::db::Db::create_connect_and_migrate(data_dir) - .await - .unwrap(); - luz = LuzHandle::new(jid.clone(), creds.password.to_string(), db); - } else { - let mut data_dir = dirs::data_dir() - .expect("operating system does not support retreiving determining default data dir"); - data_dir.push("macaw"); - data_dir.push(creds.jid.clone()); - data_dir.push(creds.jid.clone()); - // TODO: better lol - data_dir.set_extension("db"); - info!("db_path: {:?}", data_dir); - let db = luz::db::Db::create_connect_and_migrate(data_dir) - .await - .unwrap(); - luz = LuzHandle::new(jid.clone(), creds.password.to_string(), db); - } - luz -} - -#[tokio::main] -async fn main() -> iced::Result { - tracing_subscriber::fmt::init(); - - 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; - - match entry { - Ok(e) => { - let result = e.get_password(); - match result { - Ok(c) => { - let result = toml::from_str(&c); - match result { - Ok(c) => creds = Some(c), - Err(e) => { - client_creation_error = - Some(Error::CredentialsLoad(CredentialsLoadError::Toml(e.into()))) - } - } - } - Err(e) => match e { - keyring::Error::NoEntry => {} - _ => { - client_creation_error = Some(Error::CredentialsLoad( - CredentialsLoadError::Keyring(e.into()), - )) - } - }, - } - } - Err(e) => { - client_creation_error = Some(Error::CredentialsLoad(CredentialsLoadError::Keyring( - e.into(), - ))) - } - } - - let mut client: Option<(JID, LuzHandle, mpsc::Receiver<UpdateMessage>)> = None; - if let Some(creds) = creds { - let jid = creds.jid.parse::<JID>(); - match jid { - Ok(jid) => { - let (handle, updates) = luz(&jid, &creds, &cfg).await; - client = Some((jid, handle, updates)); - } - Err(e) => client_creation_error = Some(Error::CredentialsLoad(e.into())), - } - } - - if let Some((jid, luz_handle, update_recv)) = client { - let stream = ReceiverStream::new(update_recv); - let stream = stream.map(|message| Message::Luz(message)); - let task = { - 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().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_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), - ], - ) - } else { - 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_ordered_with_latest_messages().await }, - |chats| { - let chats = chats.unwrap(); - info!("got chats: {:?}", chats); - Message::GotChats(chats) - }, - ), - Task::stream(stream), - ]) - } - }; - iced::application("Macaw", Macaw::update, Macaw::view) - .subscription(subscription) - .theme(Macaw::theme) - .run_with(|| { - ( - Macaw::new( - Some(Client { - client: luz_handle, - // TODO: - jid, - // TODO: store cached status - status: Presence { - timestamp: Utc::now(), - presence: PresenceType::Offline(Offline::default()), - }, - connection_state: ConnectionState::Offline, - }), - cfg, - ), - task, - ) - }) - } else { - if let Some(e) = client_creation_error { - iced::application("Macaw", Macaw::update, Macaw::view) - .run_with(|| (Macaw::new(None, cfg), Task::done(Message::Error(e)))) - } else { - iced::application("Macaw", Macaw::update, Macaw::view) - .run_with(|| (Macaw::new(None, cfg), Task::none())) - } - } -} - -fn subscription(state: &Macaw) -> Subscription<Message> { - Subscription::batch([press_subscription(state), release_subscription(state)]) -} - -fn press_subscription(_state: &Macaw) -> Subscription<Message> { - on_key_press(handle_key_press) -} - -fn handle_key_press(key: Key, r#mod: Modifiers) -> Option<Message> { - match key { - Key::Named(iced::keyboard::key::Named::Shift) => Some(Message::ShiftPressed), - _ => None, - } -} - -fn release_subscription(_state: &Macaw) -> Subscription<Message> { - on_key_release(handle_key_release) -} - -fn handle_key_release(key: Key, r#mod: Modifiers) -> Option<Message> { - match key { - Key::Named(iced::keyboard::key::Named::Shift) => Some(Message::ShiftReleased), - _ => None, - } -} - -#[derive(Debug, Clone)] -pub enum Message { - ShiftPressed, - ShiftReleased, - LoginModal(login_modal::Message), - ClientCreated(Client), - Luz(UpdateMessage), - Roster(HashMap<JID, Contact>), - Connect, - Disconnect, - GotChats(Vec<(Chat, ChatMessage)>), - GotMessageHistory(Chat, IndexMap<Uuid, ChatMessage>), - ToggleChat(JID), - SendMessage(JID, String), - Error(Error), - MessageView(message_view::Message), -} - -#[derive(Debug, Error, Clone)] -pub enum Error { - #[error("failed to create Luz client: {0}")] - ClientCreation(#[from] luz::error::DatabaseError), - #[error("failed to save credentials: {0}")] - 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)] -pub enum CredentialsSaveError { - #[error("keyring: {0}")] - Keyring(Arc<keyring::Error>), - #[error("toml serialisation: {0}")] - Toml(#[from] toml::ser::Error), -} - -impl From<keyring::Error> for CredentialsSaveError { - fn from(e: keyring::Error) -> Self { - Self::Keyring(Arc::new(e)) - } -} - -#[derive(Debug, Error, Clone)] -pub enum CredentialsLoadError { - #[error("keyring: {0}")] - Keyring(Arc<keyring::Error>), - #[error("toml serialisation: {0}")] - Toml(#[from] toml::de::Error), - #[error("invalid jid: {0}")] - JID(#[from] jid::ParseError), -} - -impl From<keyring::Error> for CredentialsLoadError { - fn from(e: keyring::Error) -> Self { - Self::Keyring(Arc::new(e)) - } -} - -impl Macaw { - fn update(&mut self, message: Message) -> Task<Message> { - match message { - Message::Luz(update_message) => match update_message { - UpdateMessage::Error(error) => { - tracing::error!("Luz error: {:?}", error); - Task::none() - } - UpdateMessage::Online(online, vec) => match &mut self.client { - Account::LoggedIn(client) => { - client.status = Presence { - timestamp: Utc::now(), - presence: PresenceType::Online(online), - }; - client.connection_state = ConnectionState::Online; - let mut roster = HashMap::new(); - for contact in vec { - roster.insert(contact.user_jid.clone(), contact); - } - self.roster = roster; - Task::none() - } - Account::LoggedOut(login_modal) => Task::none(), - }, - UpdateMessage::Offline(offline) => { - // TODO: update all contacts' presences to unknown (offline) - match &mut self.client { - Account::LoggedIn(client) => { - client.status = Presence { - timestamp: Utc::now(), - presence: PresenceType::Offline(offline), - }; - client.connection_state = ConnectionState::Offline; - Task::none() - } - Account::LoggedOut(login_modal) => 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_jid, (chat, old_message))) = - self.chats.shift_remove_entry(&to) - { - self.chats - .insert_before(0, chat_jid, (chat, Some(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.clone()}; - self.chats.insert_before(0, to, (chat, Some(message))); - } - Task::none() - } - UpdateMessage::SubscriptionRequest(jid) => { - // TODO: subscription requests - Task::none() - } - }, - // TODO: NEXT - Message::ClientCreated(client) => { - self.client = Account::LoggedIn(client.clone()); - let client1 = client.clone(); - let client2 = client.clone(); - if self.config.auto_connect { - 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_ordered_with_latest_messages() - .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(); - info!("got chats: {:?}", chats); - Message::GotChats(chats) - }, - ), - ]) - .chain(Task::done(Message::Connect)) - } else { - 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_ordered_with_latest_messages() - .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(); - info!("got chats: {:?}", chats); - Message::GotChats(chats) - }, - ), - ]) - } - } - Message::Roster(hash_map) => { - self.roster = hash_map; - Task::none() - } - Message::Connect => match &mut self.client { - Account::LoggedIn(client) => { - client.connection_state = ConnectionState::Connecting; - let client = client.client.clone(); - Task::future(async move { - client.send(CommandMessage::Connect).await; - }) - .discard() - } - Account::LoggedOut(login_modal) => Task::none(), - }, - Message::Disconnect => match &self.client { - Account::LoggedIn(client) => { - let client = client.client.clone(); - Task::future(async move { - client - .send(CommandMessage::Disconnect(Offline::default())) - .await; - }) - .discard() - } - Account::LoggedOut(login_modal) => Task::none(), - }, - Message::ToggleChat(jid) => { - match &self.open_chat { - Some(message_view) => { - if message_view.jid == jid { - self.open_chat = None; - return Task::none(); - } - } - None => {} - } - self.open_chat = Some(MessageView::new(jid.clone(), &self.config)); - 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(), - Account::LoggedOut(login_modal) => { - let action = login_modal.update(login_modal_message); - match action { - login_modal::Action::None => Task::none(), - login_modal::Action::CreateClient(jid, password, remember_me) => { - let creds = Creds { jid, password }; - let jid = creds.jid.parse::<JID>(); - let config = self.config.clone(); - match jid { - Ok(jid) => { - Task::perform(async move { - let (jid, creds, config) = (jid, creds, config); - let (handle, recv) = luz(&jid, &creds, &config).await; - (handle, recv, jid, creds, config) - }, move |(handle, recv, jid, creds, config)| { - let creds = creds; - let mut tasks = Vec::new(); - tasks.push(Task::done(crate::Message::ClientCreated( - Client { - client: handle, - jid, - status: Presence { timestamp: Utc::now(), presence: PresenceType::Offline(Offline::default()) }, - connection_state: ConnectionState::Offline, - }, - ))); - let stream = ReceiverStream::new(recv); - let stream = - stream.map(|message| crate::Message::Luz(message)); - tasks.push(Task::stream(stream)); - - if remember_me { - let entry = Entry::new("macaw", "macaw"); - match entry { - Ok(e) => { - let creds = toml::to_string(&creds); - match creds { - Ok(c) => { - let result = e.set_password(&c); - if let Err(e) = result { - tasks.push(Task::done(crate::Message::Error( - crate::Error::CredentialsSave(e.into()), - ))); - } - } - Err(e) => tasks.push(Task::done( - crate::Message::Error( - crate::Error::CredentialsSave( - e.into(), - ), - ), - )), - } - } - Err(e) => { - tasks.push(Task::done(crate::Message::Error( - crate::Error::CredentialsSave(e.into()), - ))) - } - } - } - tasks - }).then(|tasks| Task::batch(tasks)) - } - Err(e) => Task::done(Message::LoginModal( - login_modal::Message::Error(login_modal::Error::InvalidJID), - )), - } - } - login_modal::Action::ClientCreated(task) => task, - } - } - }, - Message::GotChats(chats) => { - let mut tasks = Vec::new(); - let client = match &self.client { - Account::LoggedIn(client) => 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 { - self.chats - // TODO: could have a chat with no messages, bad database state - .insert(chat.0.correspondent.clone(), (chat.0.clone(), Some(chat.1))); - // 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 - // .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, 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, Some(message))); - } - Task::none() - } - Message::SendMessage(jid, body) => { - let client = match &self.client { - Account::LoggedIn(client) => client.clone(), - 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 }, - ) - .discard() - } - 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() - } - } - Message::ShiftPressed => { - info!("shift pressed"); - if let Some(open_chat) = &mut self.open_chat { - open_chat.shift_pressed = true; - } - Task::none() - } - Message::ShiftReleased => { - info!("shift released"); - if let Some(open_chat) = &mut self.open_chat { - open_chat.shift_pressed = false; - } - Task::none() - } - } - } - - fn view(&self) -> Element<Message> { - let mut ui: Element<Message> = { - let mut chats_list: Column<Message> = column![]; - for (jid, (chat, latest_message)) in &self.chats { - let mut open = false; - if let Some(open_chat) = &self.open_chat { - if open_chat.jid == *jid { - open = true; - } - } - let chat_list_item = chat_list_item(chat, latest_message, open); - chats_list = chats_list.push(chat_list_item); - } - let chats_list = scrollable(chats_list.spacing(8).padding(8)) - .spacing(1) - .height(Fill); - - let connection_status = self.client.connection_status(); - let client_jid: Cow<'_, str> = match &self.client { - Account::LoggedIn(client) => (&client.jid).into(), - Account::LoggedOut(_) => Cow::from("no account"), - // map(|client| (&client.jid).into()); - }; - let connected = self.client.is_connected(); - - let account_view = container(row![ - text(client_jid), - horizontal_space(), - text(connection_status), - horizontal_space().width(8), - toggler(connected).on_toggle(|connect| { - if connect { - Message::Connect - } else { - Message::Disconnect - } - }) - ]) - .padding(8); - - // TODO: config width/resizing - let sidebar = column![chats_list, account_view].height(Fill).width(300); - - let message_view; - if let Some(open_chat) = &self.open_chat { - message_view = open_chat.view().map(Message::MessageView) - } else { - message_view = column![].into(); - } - - row![sidebar, container(message_view).width(Fill)] - } - .into(); - - if let Some(new_chat) = &self.new_chat { - // TODO: close new chat window - ui = modal(ui, text("new chat"), None); - } - // temporarily center to fill space - // let ui = center(ui).into(); - let ui = container(ui).center_x(Fill).center_y(Fill); - - match &self.client { - Account::LoggedIn(_client) => ui.into(), - Account::LoggedOut(login_modal) => { - let signup = login_modal.view().map(Message::LoginModal); - modal(ui, signup, None) - } - } - } - - fn theme(&self) -> Theme { - 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), - // }, - // ))) - } -} - -fn modal<'a, Message>( - base: impl Into<Element<'a, Message>>, - content: impl Into<Element<'a, Message>>, - on_blur: Option<Message>, -) -> Element<'a, Message> -where - Message: Clone + 'a, -{ - let mut mouse_area = mouse_area(center(opaque(content)).style(|_theme| { - container::Style { - background: Some( - Color { - a: 0.8, - ..Color::BLACK - } - .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() -} - -fn chat_list_item<'a>( - chat: &'a Chat, - latest_message: &'a Option<ChatMessage>, - open: bool, -) -> Element<'a, Message> { - let mut content: Column<Message> = column![text(chat.correspondent.to_string())]; - if let Some(latest_message) = latest_message { - let message = latest_message.body.body.replace("\n", " "); - let date = latest_message.timestamp.naive_local(); - let now = Local::now().naive_local(); - let timeinfo; - if date.date() == now.date() { - // TODO: localisation/config - timeinfo = text(date.time().format("%H:%M").to_string()) - } else { - timeinfo = text(date.date().format("%d/%m").to_string()) - } - content = content.push( - row![ - container(text(message).wrapping(Wrapping::None)) - .clip(true) - .width(Fill), - timeinfo - ] - .spacing(8) - .width(Fill), - ); - } - let mut button = button(content).on_press(Message::ToggleChat(chat.correspondent.clone())); - if open { - button = button.style(|theme: &Theme, status| { - let palette = theme.extended_palette(); - button::Style::default().with_background(palette.primary.weak.color) - }); - } - button.width(Fill).into() + leptos::mount::mount_to_body(App) } |
