use std::{ borrow::Borrow, cell::RefCell, collections::HashMap, marker::PhantomData, ops::{Deref, DerefMut}, rc::Rc, str::FromStr, sync::{atomic::AtomicUsize, Arc}, thread::sleep, time::{self, Duration}, }; use filamento::{ chat::{Chat, Message}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::FilesMem, user::User, UpdateMessage, }; use futures::stream::StreamExt; use indexmap::IndexMap; use jid::JID; use leptos::{ prelude::*, task::{spawn, spawn_local}, }; use leptos_meta::Stylesheet; use reactive_stores::Store; use stylance::import_style; use thiserror::Error; use tokio::sync::{mpsc::Receiver, Mutex}; use tracing::debug; use uuid::Uuid; pub enum AppState { LoggedOut, LoggedIn, } #[derive(Clone)] pub struct Client { client: filamento::Client, jid: Arc, file_store: FilesMem, } impl Deref for Client { type Target = filamento::Client; fn deref(&self) -> &Self::Target { &self.client } } impl DerefMut for Client { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.client } } #[component] pub fn App() -> impl IntoView { let (app, set_app) = signal(AppState::LoggedOut); let client: RwSignal)>> = RwSignal::new(None); view! { {move || match &*app.read() { AppState::LoggedOut => view! { }.into_any(), AppState::LoggedIn => { if let Some((client, updates)) = client.write_untracked().take() { view! { }.into_any() } else { set_app.set(AppState::LoggedOut); view! { }.into_any() } } }} } } #[derive(Clone, Debug, Error)] pub enum LoginError { #[error("Missing Password")] MissingPassword, #[error("Missing JID")] MissingJID, #[error("Invalid JID: {0}")] InvalidJID(#[from] jid::ParseError), #[error("Connection Error: {0}")] ConnectionError(#[from] CommandError), } #[component] fn LoginPage(set_app: WriteSignal, set_client: RwSignal)>>) -> impl IntoView { let jid = RwSignal::new("".to_string()); let password = RwSignal::new("".to_string()); let remember_me = RwSignal::new(false); let connect_on_login = RwSignal::new(true); let (error, set_error) = signal(None::); let error_message = move || { error.with(|error| { if let Some(error) = error { view! {
{error.to_string()}
}.into_any() } else { view! {}.into_any() } }) }; let (login_pending, set_login_pending) = signal(false); let login = Action::new_local(move |_| { async move { set_login_pending.set(true); if jid.read_untracked().is_empty() { set_error.set(Some(LoginError::MissingJID)); set_login_pending.set(false); return } if password.read_untracked().is_empty() { set_error.set(Some(LoginError::MissingPassword)); set_login_pending.set(false); return } let jid = match JID::from_str(&jid.read_untracked()) { Ok(j) => j, Err(e) => { set_error.set(Some(e.into())); set_login_pending.set(false); return }, }; // initialise the client let db = Db::create_connect_and_migrate("mem.db").await.unwrap(); let files_mem = FilesMem::new(); let (client, updates) = filamento::Client::new(jid.clone(), password.read_untracked().clone(), db, files_mem.clone()); // TODO: remember_me let client = Client { client, jid: Arc::new(jid), file_store: files_mem, }; if *connect_on_login.read_untracked() { match client.connect().await { Ok(_) => {}, Err(e) => { set_error.set(Some(e.into())); set_login_pending.set(false); return }, } } // debug!("before setting app state"); set_client.set(Some((client, updates))); set_app.set(AppState::LoggedIn); } }); view! {

Macaw Instant Messenger

{error_message}
} } #[component] fn Macaw( // TODO: logout // app_state: WriteSignal)>, LocalStorage>, client: Client, mut updates: Receiver, ) -> impl IntoView { provide_context(client); let (new_messages, set_new_messages) = signal(None::<(JID, MacawMessage)>); provide_context(new_messages); let messages_store: StateStore> = StateStore::new(); provide_context(RwSignal::new_local(messages_store)); let chats_store: StateStore> = StateStore::new(); provide_context(RwSignal::new_local(chats_store)); let users_store: StateStore> = StateStore::new(); provide_context(RwSignal::new_local(users_store)); // // here we create a signal in the root that can be consumed // // anywhere in the app. // let (count, set_count) = signal(0); // // we'll pass the setter to specific components, // // but provide the count itself to the whole app via context // provide_context(count); OnceResource::new(async move { while let Some(update) = updates.recv().await { match update { UpdateMessage::Online(online, items) => {}, UpdateMessage::Offline(offline) => {}, UpdateMessage::RosterUpdate(contact, user) => {}, UpdateMessage::RosterDelete(jid) => {}, UpdateMessage::Presence { from, presence } => {}, UpdateMessage::Message { to, from, message } => { let new_message = MacawMessage::got_message_and_user(message, from); set_new_messages.set(Some((to, new_message))); }, UpdateMessage::MessageDelivery { id, chat, delivery } => {}, UpdateMessage::SubscriptionRequest(jid) => {}, UpdateMessage::NickChanged { jid, nick } => {}, UpdateMessage::AvatarChanged { jid, id } => {}, } } }); view! { } } // V has to be an arc signal struct StateStore { store: Rc>>, } impl Clone for StateStore { fn clone(&self) -> Self { Self { store: self.store.clone(), } } } impl StateStore { pub fn new() -> Self { Self { store: Rc::new(RefCell::new(HashMap::new())), } } } impl StateStore { pub fn store(&self, key: K, value: V) -> StateListener { let mut store = self.store.borrow_mut(); if let Some((v, count)) = store.get_mut(&key) { *v = value.clone(); *count += 1; StateListener { value, cleaner: StateCleaner { key, _ty: PhantomData }, } } else { store.insert(key.clone(), (value.clone(), 1)); StateListener { value, cleaner: StateCleaner { key, _ty: PhantomData, } } } } pub fn init() { } } impl StateStore { pub fn update(&self, key: &K, value: V) { if let Some((v, _)) = self.store.borrow_mut().get_mut(key) { *v = value; } } pub fn modify(&self, key: &K, modify: impl Fn(&mut V)) { if let Some((v, _)) = self.store.borrow_mut().get_mut(key) { modify(v); } } fn remove(&self, key: &K) { let mut store = self.store.borrow_mut(); if let Some((_v, count)) = store.get_mut(key) { *count -= 1; if *count == 0 { store.remove(key); } } } } #[derive(Clone)] struct StateListener where K: Eq + std::hash::Hash + 'static, V: 'static { value: V, cleaner: StateCleaner } impl Deref for StateListener { type Target = V; fn deref(&self) -> &Self::Target { &self.value } } impl DerefMut for StateListener { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } } struct StateCleaner where K: Eq + std::hash::Hash + 'static, V: 'static { key: K, _ty: PhantomData, // state_store: StateStore, } impl Clone for StateCleaner where K: Eq + std::hash::Hash + Clone { fn clone(&self) -> Self { let state_store = use_context::, LocalStorage>>().unwrap(); if let Some((_v, count)) = state_store.read_untracked().store.borrow_mut().get_mut(&self.key) { *count += 1; } Self { key: self.key.clone(), _ty: PhantomData, } } } impl Drop for StateCleaner { fn drop(&mut self) { let state_store = use_context::, LocalStorage>>().unwrap(); state_store.read_untracked().remove(&self.key) } } #[derive(Clone)] struct MacawChat { chat: StateListener>, user: StateListener>, } impl MacawChat { fn got_chat_and_user(chat: Chat, user: User) -> Self { let chat_state_store: RwSignal>, LocalStorage> = use_context().expect("no chat state store"); let user_state_store: RwSignal>, LocalStorage> = use_context().expect("no user state store"); let user = user_state_store.read_untracked().store(user.jid.clone(), Store::new(user)); let chat = chat_state_store.read_untracked().store(chat.correspondent.clone(), Store::new(chat)); Self { chat, user, } } } impl Deref for MacawChat { type Target = StateListener>; fn deref(&self) -> &Self::Target { &self.chat } } impl DerefMut for MacawChat { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.chat } } #[derive(Clone)] struct MacawMessage { message: StateListener>, user: StateListener>, } impl MacawMessage { fn got_message_and_user(message: Message, user: User) -> Self { let message_state_store: RwSignal>, LocalStorage> = use_context().expect("no message state store"); let user_state_store: RwSignal>, LocalStorage> = use_context().expect("no user state store"); let message = message_state_store.read_untracked().store(message.id, Store::new(message)); let user = user_state_store.read_untracked().store(user.jid.clone(), Store::new(user)); Self { message, user, } } } impl Deref for MacawMessage { type Target = StateListener>; fn deref(&self) -> &Self::Target { &self.message } } impl DerefMut for MacawMessage { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.message } } struct MacawUser { user: StateListener>, } impl Deref for MacawUser { type Target = StateListener>; fn deref(&self) -> &Self::Target { &self.user } } impl DerefMut for MacawUser { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.user } } #[component] fn ChatsList() -> impl IntoView { let client = use_context::().expect("client not in context"); let (chats, set_chats) = signal(IndexMap::new()); let load_chats = LocalResource::new(move || async move { let client = use_context::().expect("client not in context"); let chats = client.get_chats_ordered_with_latest_messages_and_users().await.map_err(|e| e.to_string()); match chats { Ok(c) => { let chats = c.into_iter().map(|((chat, chat_user), (message, message_user))| { (chat.correspondent.clone(), (MacawChat::got_chat_and_user(chat, chat_user), MacawMessage::got_message_and_user(message, message_user))) }).collect::>(); set_chats.set(chats); }, Err(_) => { // TODO: show error message at top of chats list }, } }); // TODO: filter new messages signal let new_messages_signal: ReadSignal> = use_context().unwrap(); spawn_local(async move { let mut new_message = new_messages_signal.to_stream(); load_chats.await; while let Some(new_message) = new_message.next().await { debug!("got new message in let"); if let Some((to, new_message)) = new_message { if let Some((chat, _latest_message)) = set_chats.write().shift_remove(&to) { debug!("chat existed"); set_chats.write().insert_before(0, to, (chat, new_message)); debug!("done setting"); } else { debug!("the chat didn't exist"); let chat = client.get_chat(to.clone()).await.unwrap(); let user = client.get_user(to.clone()).await.unwrap(); let chat = MacawChat::got_chat_and_user(chat, user); set_chats.write().insert_before(0, to, (chat, new_message)); debug!("done setting"); } } } debug!("set the new message"); }); view! {

Chats

{chat.0.to_string()}

} }