use std::collections::{HashMap, HashSet};
use iced::futures::{SinkExt, Stream, StreamExt};
use iced::widget::{button, column, row, text, text_input};
use iced::{stream, Element, Subscription, Task, Theme};
use jid::JID;
use luz::chat::{Chat, Message as ChatMessage};
use luz::presence::{Offline, Presence};
use luz::CommandMessage;
use luz::{roster::Contact, user::User, LuzHandle, UpdateMessage};
use tokio::sync::oneshot;
use tokio_stream::wrappers::ReceiverStream;
#[derive(Default)]
pub struct Macaw {
client: Option<LuzHandle>,
roster: HashMap<JID, Contact>,
users: HashMap<JID, User>,
presences: HashMap<JID, Presence>,
chats: HashMap<JID, (Chat, Vec<ChatMessage>)>,
subscription_requests: HashSet<JID>,
connection_status: Option<Presence>,
}
fn main() -> iced::Result {
tracing_subscriber::fmt::init();
iced::application("Macaw", Macaw::update, Macaw::view)
.subscription(Macaw::subscription)
.run()
}
#[derive(Debug, Clone)]
enum Message {
ClientCreated(LuzHandle),
Luz(UpdateMessage),
Roster(HashMap<JID, Contact>),
Connect,
Disconnect,
OpenChat(JID),
}
impl Macaw {
fn stream() -> impl Stream<Item = Message> {
stream::channel(100, |mut output| async {
let (luz, recv) = LuzHandle::new(
"test@blos.sm".try_into().unwrap(),
"slayed".to_string(),
"./macaw.db",
)
.await
.unwrap();
output.send(Message::ClientCreated(luz)).await;
let stream = ReceiverStream::new(recv);
let stream = stream.map(|message| Message::Luz(message)).map(Ok);
stream.forward(output).await;
})
}
fn subscription(&self) -> Subscription<Message> {
Subscription::run(Macaw::stream)
}
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) => {
self.connection_status = Some(Presence::Online(online));
let mut roster = HashMap::new();
for contact in vec {
roster.insert(contact.user_jid.clone(), contact);
}
self.roster = roster;
Task::none()
}
UpdateMessage::Offline(offline) => {
self.connection_status = Some(Presence::Offline(offline));
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, message_history)) = self.chats.get_mut(&to) {
message_history.push(message);
} else {
let chat = Chat {
correspondent: to.clone(),
};
let message_history = vec![message];
self.chats.insert(to, (chat, message_history));
}
Task::none()
}
UpdateMessage::SubscriptionRequest(jid) => {
// TODO: subscription requests
Task::none()
}
},
Message::ClientCreated(luz_handle) => {
let cloned: LuzHandle = luz_handle.clone();
self.client = Some(cloned);
let (send, recv) = oneshot::channel();
Task::perform(
async move {
luz_handle.send(CommandMessage::GetRoster(send)).await;
recv.await
},
|result| {
let roster = result.unwrap().unwrap();
let mut macaw_roster = HashMap::new();
for contact in roster {
macaw_roster.insert(contact.user_jid.clone(), contact);
}
Message::Roster(macaw_roster)
},
)
}
Message::Roster(hash_map) => {
self.roster = hash_map;
Task::none()
}
Message::Connect => {
let client = self.client.clone();
Task::future(async move {
client.clone().unwrap().send(CommandMessage::Connect).await;
})
.discard()
}
Message::Disconnect => {
let client = self.client.clone();
Task::future(async move {
client
.clone()
.unwrap()
.send(CommandMessage::Disconnect(Offline::default()))
.await;
})
.discard()
}
Message::OpenChat(jid) => todo!(),
}
}
fn view(&self) -> Element<Message> {
let mut contacts: Vec<Element<Message>> = Vec::new();
for (_, contact) in &self.roster {
contacts.push(
button(match &contact.user_jid.localpart {
Some(u) => u,
None => "no username",
})
.on_press(Message::OpenChat(contact.user_jid.clone()))
.into(),
);
}
let column = column(contacts);
let connection_status = match &self.connection_status {
Some(s) => match s {
Presence::Online(online) => "connected",
Presence::Offline(offline) => "disconnected",
},
None => "no account",
};
column![
row![
text("test@blos.sm"),
text(connection_status),
button("connect").on_press(Message::Connect),
button("disconnect").on_press(Message::Disconnect)
],
text("Buddy List:"),
//
//
column,
]
.into()
}
fn theme(&self) -> Theme {
Theme::Dark
}
}