use std::{collections::HashSet, str::FromStr};
use filamento::{error::{CommandError, SubscribeError}, roster::ContactStoreFields};
use jid::{BareJID, JID};
use leptos::{html::Input, prelude::*};
use reactive_stores::Store;
use thiserror::Error;
use crate::{client::Client, roster::{Roster, RosterStoreFields}};
#[derive(Clone, Debug, Error)]
pub enum AddContactError {
#[error("Missing JID")]
MissingJID,
#[error("Invalid JID: {0}")]
InvalidJID(#[from] jid::ParseError),
#[error("Subscription: {0}")]
Db(#[from] CommandError<SubscribeError>),
}
#[component]
// TODO: rename
pub fn AddContact() -> impl IntoView {
let requests: ReadSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions in context");
let set_requests: WriteSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions write signal in context");
let roster: Store<Roster> = use_context().expect("no roster in context");
let jid = RwSignal::new("".to_string());
// TODO: compartmentalise into error component, form component...
let (error, set_error) = signal(None::<AddContactError>);
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 (add_contact_pending, set_add_contact_pending) = signal(false);
let client = use_context::<Client>().expect("client not in context");
let client2 = client.clone();
let client3 = client.clone();
let client4 = client.clone();
let add_contact= Action::new_local(move |_| {
let client = client.clone();
async move {
set_add_contact_pending.set(true);
if jid.read_untracked().is_empty() {
set_error.set(Some(AddContactError::MissingJID));
set_add_contact_pending.set(false);
return;
}
let jid = match JID::from_str(&jid.read_untracked()) {
Ok(j) => j.to_bare(),
Err(e) => {
set_error.set(Some(e.into()));
set_add_contact_pending.set(false);
return;
}
};
let chat_jid = jid;
// TODO: more options?
match client.buddy_request(chat_jid).await {
Ok(c) => c,
Err(e) => {
set_error.set(Some(e.into()));
set_add_contact_pending.set(false);
return;
},
};
set_add_contact_pending.set(false);
}
});
let jid_input = NodeRef::<Input>::new();
let _focus = Effect::new(move |_| {
if let Some(input) = jid_input.get() {
let _ = input.focus();
input.set_text_content(Some(""));
// input.style("height: 0");
// let height = input.scroll_height();
// input.style(format!("height: {}px", height));
}
});
let outgoing = move || roster.contacts().get().into_iter().filter(|(jid, contact)| {
match *contact.contact.subscription().read() {
filamento::roster::Subscription::None => false,
filamento::roster::Subscription::PendingOut => true,
filamento::roster::Subscription::PendingIn => false,
filamento::roster::Subscription::PendingInPendingOut => true,
filamento::roster::Subscription::OnlyOut => false,
filamento::roster::Subscription::OnlyIn => false,
filamento::roster::Subscription::OutPendingIn => false,
filamento::roster::Subscription::InPendingOut => true,
filamento::roster::Subscription::Buddy => false,
}
}).collect::<Vec<_>>();
let accept_friend_request = Action::new_local(move |jid: &BareJID| {
let client = client2.clone();
let jid = jid.clone();
async move {
// TODO: error
client.accept_buddy_request(jid).await;
}
});
let reject_friend_request = Action::new_local(move |jid: &BareJID| {
let client = client3.clone();
let jid = jid.clone();
async move {
// TODO: error
client.unsubscribe_contact(jid.clone()).await;
set_requests.write().remove(&jid);
}
});
let cancel_subscription_request = Action::new_local(move |jid: &BareJID| {
let client = client4.clone();
let jid = jid.clone();
async move {
// TODO: error
client.unsubscribe_from_contact(jid).await;
}
});
view! {
<div class="add-contact-menu">
<div>
{error_message}
<form on:submit=move |ev| {
ev.prevent_default();
add_contact.dispatch(());
}>
<input
disabled=add_contact_pending
placeholder="JID"
type="text"
node_ref=jid_input
bind:value=jid
name="jid"
id="jid"
autofocus="true"
/>
<input disabled=add_contact_pending class="button" type="submit" value="Send Friend Request" />
</form>
</div>
{move || if !requests.read().is_empty() {
view! {
<div>
<h3>Incoming Subscription Requests</h3>
<For each=move || requests.get() key=|request| request.clone() let(request)>
{
let request2 = request.clone();
let request3 = request.clone();
let jid_string = move || request.to_string();
view! {
<div class="jid-with-button"><div class="jid">{jid_string}</div>
<div><div class="button" on:click=move |_| { accept_friend_request.dispatch(request2.clone()); } >Accept</div><div class="button" on:click=move |_| { reject_friend_request.dispatch(request3.clone()); } >Reject</div></div></div>
}
}
</For>
</div>
}.into_any()
} else {
view! {}.into_any()
}}
{move || if !outgoing().is_empty() {
view! {
<div>
<h3>Pending Outgoing Subscription Requests</h3>
<For each=move || outgoing() key=|(jid, _contact)| jid.clone() let((jid, contact))>
{
let jid2 = jid.clone();
let jid_string = move || jid.to_string();
view! {
<div class="jid-with-button"><div class="jid">{jid_string}</div><div class="button" on:click=move |_| { cancel_subscription_request.dispatch(jid2.clone()); } >Cancel</div></div>
}
}
</For>
</div>
}.into_any()
} else {
view! {}.into_any()
}}
</div>
}
}