use std::{ops::{Deref, DerefMut}, str::FromStr, sync::Arc, thread::sleep, time::{self, Duration}};
use filamento::{db::Db, files::FilesMem, UpdateMessage};
use jid::JID;
use leptos::{logging::debug_warn, prelude::*, task::spawn_local};
use leptos_meta::Stylesheet;
use stylance::import_style;
use thiserror::Error;
use tokio::{sync::mpsc::Receiver};
pub enum AppState {
LoggedOut,
LoggedIn,
}
#[derive(Clone)]
pub struct Client {
client: filamento::Client<FilesMem>,
jid: Arc<JID>,
file_store: FilesMem,
}
impl Deref for Client {
type Target = filamento::Client<FilesMem>;
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<Option<(Client, Receiver<UpdateMessage>)>, LocalStorage> = RwSignal::new_local(None);
view! {
{move || match &*app.read() {
AppState::LoggedOut => view! { <LoginPage set_app set_client=client /> }.into_any(),
AppState::LoggedIn => {
if let Some((client, updates)) = client.write_untracked().take() {
debug_warn!("opening app");
view! { <Macaw client updates /> }.into_any()
} else {
set_app.set(AppState::LoggedOut);
view! { <LoginPage set_app set_client=client /> }.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),
}
#[component]
fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client, Receiver<UpdateMessage>)>, LocalStorage>) -> 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::<LoginError>);
let error_message = move || {
error.with(|error| {
if let Some(error) = error {
view! { <div class="error">{error.to_string()}</div> }.into_any()
} else {
view! {}.into_any()
}
})
};
let (login_pending, set_login_pending) = signal(false);
let login = Action::new(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
// TODO: connect on login
let client = Client {
client,
jid: Arc::new(jid),
file_store: files_mem,
};
// debug!("before setting app state");
set_client.set(Some((client, updates)));
debug_warn!("going");
set_app.set(AppState::LoggedIn);
debug_warn!("gone");
}
});
view! {
<div class="center fill">
<div id="login-form" class="panel">
<div id="hero">
<img src="/assets/icon.png" />
<h1>Macaw Instant Messenger</h1>
</div>
{error_message}
<form on:submit=move |ev| {
ev.prevent_default();
login.dispatch(());
}>
<label for="jid">JID</label>
<input
disabled=login_pending
placeholder="caw@macaw.chat"
type="text"
bind:value=jid
name="jid"
id="jid"
autofocus="true"
/>
<label for="password">Password</label>
<input
disabled=login_pending
placeholder="••••••••"
type="password"
bind:value=password
name="password"
id="password"
/>
<div>
<label for="remember_me">Remember me</label>
<input
disabled=login_pending
type="checkbox"
bind:checked=remember_me
name="remember_me"
id="remember_me"
/>
</div>
<div>
<label for="connect_on_login">Connect on login</label>
<input
disabled=login_pending
type="checkbox"
bind:checked=connect_on_login
name="connect_on_login"
id="connect_on_login"
/>
</div>
<input disabled=login_pending class="button" type="submit" value="Log In" />
</form>
</div>
</div>
}
}
#[component]
fn Macaw(
// TODO: logout
// app_state: WriteSignal<Option<essage>)>, LocalStorage>,
client: Client,
mut updates: Receiver<UpdateMessage>,
) -> impl IntoView {
let client = RwSignal::new_local(client);
provide_context(client);
debug_warn!("ajsdlkfjalsf");
let updates_routine = OnceResource::new(async move {
debug_warn!("started");
while let Some(update) = updates.recv().await {
match update {
UpdateMessage::Online(online, items) => todo!(),
UpdateMessage::Offline(offline) => todo!(),
UpdateMessage::RosterUpdate(contact, user) => todo!(),
UpdateMessage::RosterDelete(jid) => todo!(),
UpdateMessage::Presence { from, presence } => todo!(),
UpdateMessage::Message { to, from, message } => todo!(),
UpdateMessage::MessageDelivery { id, chat, delivery } => todo!(),
UpdateMessage::SubscriptionRequest(jid) => todo!(),
UpdateMessage::NickChanged { jid, nick } => todo!(),
UpdateMessage::AvatarChanged { jid, id } => todo!(),
}
}
});
view! { "logged in" }
}