use std::{borrow::Cow, collections::HashMap}; use crossbeam_channel::Receiver; use eframe::egui::{self, Color32, ScrollArea, Sense}; use matrix_sdk::{ deserialized_responses::SyncResponse, room::Room, ruma::{events::AnyRoomEvent, RoomId, UserId}, Client, RoomMember, }; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::UnboundedSender; use crate::sync; use super::Id; /// Logged in application state #[derive(Debug)] pub struct App { pub client: matrix_sdk::Client, pub request: UnboundedSender, pub response: Receiver, pub sync_handle: Option>, pub error: Option, pub room_list: RoomList, pub timelines: HashMap, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct RoomList { pub selected_room: Option, pub room_name: HashMap, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Timeline { messages: Vec, #[serde(skip)] member: HashMap, } impl RoomList { fn room_name<'a>(&'a self, room: &matrix_sdk::BaseRoom) -> Cow<'a, str> { if let Some(name) = self.room_name.get(room.room_id()) { name.into() } else if let Some(name) = room.name() { name.into() } else if let Some(alias) = room.canonical_alias() { String::from(alias).into() } else { room.room_id().to_string().into() } } fn is_selected(&self, room_id: &RoomId) -> bool { self.selected_room .as_ref() .map_or(false, |id| id == room_id) } fn selected_room(&self, client: &Client) -> Option { match self.selected_room { Some(ref selected) => client.get_room(selected), None => None, } } } impl App { pub fn new( client: Client, request: UnboundedSender, response: Receiver, sync_handle: std::thread::JoinHandle<()>, ) -> Self { Self { client, request, response, sync_handle: Some(sync_handle), room_list: RoomList::default(), error: None, timelines: HashMap::new(), } } pub fn update(&mut self, ctx: &egui::CtxRef) { if let Ok(response) = self.response.try_recv() { self.handle_response(response); } egui::SidePanel::left(Id::RoomPanel) .max_width(800.0) .default_width(400.0) .show(ctx, |ui| { ui.add(egui::Label::new("Joined").strong()); for room in self.client.joined_rooms() { let group = if self.room_list.is_selected(room.room_id()) { egui::Frame::group(&Default::default()) } else { egui::Frame::group(&Default::default()).fill(Color32::from_rgb(0, 0, 0)) }; let response = group.show(ui, |ui| { ui.set_width(ui.available_width()); let name = self.room_list.room_name(&*room); ui.label(&*name); }); let response = response.response.interact(Sense::click()); if response.clicked() { self.room_list.selected_room = Some(room.room_id().clone()); } } }); let room = match self.room_list.selected_room(&self.client) { Some(room) => room, _ => return, }; egui::TopBottomPanel::top(Id::RoomSummary).show(ctx, |ui| { ui.horizontal(|ui| { ui.set_width(ui.available_width()); ui.heading(&*self.room_list.room_name(&room)); if let Some(ref target) = room.direct_target() { ui.label(target.as_str()); } else if let Some(ref alias) = room.canonical_alias() { ui.label(alias.as_str()); } if let Some(ref topic) = room.topic() { ui.label(topic); } }) }); egui::SidePanel::right(Id::MemberList).show(ctx, |ui| { ui.heading("Members"); }); let room = match room { Room::Joined(room) => room, _ => return, }; let timeline = self.timelines.entry(room.room_id().clone()).or_default(); egui::CentralPanel::default().show(ctx, |ui| { ScrollArea::auto_sized().show(ui, |ui| { for message in timeline.messages.iter() { ui.label(message.sender().as_str()); } }) }); } fn handle_response(&mut self, response: sync::Response) { match response { sync::Response::RoomName(room, name) => { self.room_list.room_name.insert(room, name); } sync::Response::Error(e) => { self.error = Some(e.to_string()); } sync::Response::Sync(sync) => self.handle_sync(sync), }; } fn handle_sync(&mut self, sync: SyncResponse) { for (id, room) in sync.rooms.join { let timeline = self.timelines.entry(id.clone()).or_default(); for event in room.timeline.events { let event = match event.event.deserialize() { Ok(event) => event, Err(_) => continue, }; timeline.messages.push(event.into_full_event(id.clone())); } } } }