diff --git a/src/_base.scss b/src/_base.scss index 233d29a..6dbde92 100644 --- a/src/_base.scss +++ b/src/_base.scss @@ -1,3 +1,6 @@ +$font-size: 100vh * 0.01; +$icon-size: $font-size * 2; + body { height: 100vh; width: 100vw; diff --git a/src/base.rs b/src/base.rs index b3dc3ac..9b1da7b 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,18 +1,114 @@ -use std::sync::Arc; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; + +use dioxus_std::utils::rw::UseRw; +use matrix_sdk::room::Room as MatrixRoom; +use matrix_sdk::{ + room::RoomMember, + ruma::{OwnedMxcUri, OwnedRoomId, OwnedUserId}, +}; use crate::matrix_client::Requester; -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Store { - pub is_logged: bool, +#[derive(Clone, Debug)] +pub struct UserInfo { + pub avatar_url: Option, + pub display_name: Option, + pub blurhash: Option, } -impl Store { - pub fn new() -> Self { - Self { is_logged: false } +impl UserInfo { + pub fn new( + avatar_url: Option, + display_name: Option, + blurhash: Option, + ) -> Self { + Self { + avatar_url, + display_name, + blurhash, + } } } +#[derive(Clone, Debug)] +pub struct Room { + pub matrix_room: Arc, + pub topic: Option, + pub members: HashMap, + pub is_direct: Option, +} + +impl Room { + pub fn new( + matrix_room: Arc, + topic: Option, + is_direct: Option, + ) -> Self { + Self { + matrix_room, + topic, + members: HashMap::new(), + is_direct, + } + } + + pub fn name(&self) -> Option { + self.matrix_room.name() + } +} + +impl PartialEq for Room { + fn eq(&self, other: &Self) -> bool { + // TODO: Look for a better way to compare Matrix rooms + self.matrix_room.room_id() == other.matrix_room.room_id() + && self.topic == other.topic + && self.is_direct == other.is_direct + } +} + +pub type ByIdRooms = HashMap>>; +pub type ByIdUserInfos = HashMap; + +#[derive(Clone, Debug)] +pub struct Store { + pub is_logged: bool, + pub rooms: ByIdRooms, + pub user_infos: ByIdUserInfos, + pub user_id: Option, +} + +impl<'a> Store { + pub fn new() -> Self { + Self { + is_logged: false, + rooms: HashMap::new(), + user_infos: HashMap::new(), + user_id: None, + } + } +} + +impl PartialEq for Store { + fn eq(&self, other: &Self) -> bool { + self.is_logged == other.is_logged + && self.user_id == other.user_id + && self.user_infos.len() == other.user_infos.len() + && self + .user_infos + .keys() + .all(|k| other.user_infos.contains_key(k)) + && self.rooms.len() == other.rooms.len() + && self.rooms.keys().all(|k| other.rooms.contains_key(k)) + } +} + +impl Eq for Store {} + +pub type ReactiveStore = Arc>; + #[derive(Clone)] pub struct AppSettings { pub requester: Option>, diff --git a/src/components/contacts.rs b/src/components/contacts.rs deleted file mode 100644 index 3b8ab36..0000000 --- a/src/components/contacts.rs +++ /dev/null @@ -1,86 +0,0 @@ -use dioxus::prelude::*; -use dioxus_free_icons::icons::io_icons::IoChevronDown; -use dioxus_free_icons::Icon; - -turf::style_sheet!("src/components/contacts.scss"); - -fn ContactsArrow(cx: Scope) -> Element { - cx.render(rsx! { - style { STYLE_SHEET }, - - Icon { - icon: IoChevronDown, - }, - }) -} - -pub fn Contacts(cx: Scope) -> Element { - let show_contacts = use_state(cx, || false); - - let classes = vec![ - ClassName::CONTACTS, - if **show_contacts { - ClassName::ACTIVE - } else { - "" - }, - ] - .join(" "); - - cx.render(rsx! { - style { STYLE_SHEET }, - - div { - class: "{classes}", - - p { - class: ClassName::HEADER, - onclick: move |_| show_contacts.set(!show_contacts), - - ContactsArrow {}, - - "Online (4)", - }, - - // TODO: Test overflow - ul { - li { - img { - src: "./images/status_online.png", - }, - p { - "Contact AAAAAAAA -", - }, - p { - style: "color: darkgrey;", - "i'm sad all day until i get to talk with friends, online friends that is", - }, - }, - li { - img { - src: "./images/status_busy.png", - }, - p { - "Contact BBBBBB -", - }, - p { - style: "color: darkgrey;", - "i'm sad all day until i get to talk with friends, online friends that is", - } - }, - li { - img { - src: "./images/status_away.png", - }, - p { - "Contact CCC -", - }, - p { - style: "color: darkgrey;", - "i'm sad all day until i get to talk with friends, online friends that is", - } - }, - }, - }, - }) -} diff --git a/src/components/contacts.scss b/src/components/contacts.scss deleted file mode 100644 index e44094d..0000000 --- a/src/components/contacts.scss +++ /dev/null @@ -1,65 +0,0 @@ -.contacts { - height: 72%; - width: 100%; - background-color: white; - font-size: 8pt; - - &.active { - ul { - opacity: 0; - } - - svg { - transform: rotate(180deg); - } - - } - - .header { - height: 2%; - width: 98%; - display: flex; - flex-direction: row; - align-items: center; - cursor: pointer; - margin: 0; - margin-left: 1%; - padding-top: 1%; - font-weight: bold; - } - - ul { - height: 100%; - margin: 0; - overflow: hidden; - opacity: 1; - transition: 0.4s ease; - } - - li { - list-style-type: none; - height: 2%; - margin: 0 auto; - cursor: pointer; - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - - img { - height: 100%; - aspect-ratio: 1; - } - } - - svg { - transition: 0.4s ease; - } - - .contact { - list-style-type: none; - margin: 0 auto; - text-align: left; - cursor: pointer - } -} diff --git a/src/components/contacts_window/contacts.rs b/src/components/contacts_window/contacts.rs new file mode 100644 index 0000000..0d16bb6 --- /dev/null +++ b/src/components/contacts_window/contacts.rs @@ -0,0 +1,47 @@ +use std::cell::RefCell; + +use dioxus::prelude::*; +use dioxus_std::utils::rw::UseRw; + +use crate::base::Room; +use crate::base::Store; +use crate::components::contacts_window::contacts_section::ContactsSection; + +turf::style_sheet!("src/components/contacts_window/contacts.scss"); + +#[inline_props] +pub fn Contacts<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { + println!("Contacts rendering"); + + let store = rw_store.read().unwrap(); + + let rooms = &store.rooms; + + let rooms_len = rooms.len(); + let mut groups = Vec::::with_capacity(rooms_len); + let mut directs = Vec::::with_capacity(rooms_len); + + for arc_room in rooms.values() { + let room = arc_room.read().unwrap().to_owned(); + + let is_direct = room.is_direct.unwrap(); + if is_direct { + directs.push(room); + } else { + groups.push(room); + } + } + + // TODO: Test overflow + // TODO: Add offline users ? + cx.render(rsx! { + style { STYLE_SHEET }, + + div { + class: ClassName::CONTACTS, + + ContactsSection {name: "Groups", contacts: RefCell::new(groups)}, + ContactsSection {name: "Available", contacts: RefCell::new(directs)}, + }, + }) +} diff --git a/src/components/contacts_window/contacts.scss b/src/components/contacts_window/contacts.scss new file mode 100644 index 0000000..25af475 --- /dev/null +++ b/src/components/contacts_window/contacts.scss @@ -0,0 +1,6 @@ +@import "../../_base.scss" + +.contacts { + height: 72%; + background-color: white; +} diff --git a/src/components/contacts_window/contacts_section.rs b/src/components/contacts_window/contacts_section.rs new file mode 100644 index 0000000..a89b2ed --- /dev/null +++ b/src/components/contacts_window/contacts_section.rs @@ -0,0 +1,87 @@ +use std::cell::RefCell; + +use dioxus::prelude::*; +use dioxus_free_icons::icons::io_icons::IoChevronDown; +use dioxus_free_icons::Icon; +use matrix_sdk::RoomState; + +use crate::base::Room; + +turf::style_sheet!("src/components/contacts_window/contacts_section.scss"); + +fn ContactsArrow(cx: Scope) -> Element { + cx.render(rsx! { + style { STYLE_SHEET }, + + Icon { + icon: IoChevronDown, + }, + }) +} + +static NO_SUBJECT_REPR: &str = "No subject"; + +#[inline_props] +pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell>) -> Element { + println!("ContactsSection rendering"); + + let show = use_state(cx, || false); + + let classes = vec![ + ClassName::SECTION, + if **show { ClassName::ACTIVE } else { "" }, + ] + .join(" "); + + let contacts_len = contacts.borrow().len(); + + let rendered_contacts = contacts.borrow_mut().clone().into_iter().map(|room| { + let room_name = room.name().unwrap(); + let is_invited = room.matrix_room.state() == RoomState::Invited; + let formatted = format!( + "{room_name} - {}", + if is_invited { + format!("Invited - ") + } else { + "".to_string() + } + ); + + let room_topic = room.topic.unwrap_or(NO_SUBJECT_REPR.to_string()).to_owned(); + + rsx!(li { + img { + src: "./images/status_online.png", + }, + p { + formatted, + }, + p { + style: "color: darkgrey;", + room_topic, + }, + }, + ) + }); + + cx.render(rsx! { + style { STYLE_SHEET }, + + div { + class: "{classes}", + + p { + class: ClassName::HEADER, + onclick: move |_| show.set(!show), + + ContactsArrow {}, + + format!("{name} ({contacts_len})"), + }, + + ul { + rendered_contacts.into_iter(), + }, + }, + }) +} diff --git a/src/components/contacts_window/contacts_section.scss b/src/components/contacts_window/contacts_section.scss new file mode 100644 index 0000000..24b7531 --- /dev/null +++ b/src/components/contacts_window/contacts_section.scss @@ -0,0 +1,67 @@ +@import "../../_base.scss" + +.section { + width: 100%; + font-size: $font-size; + + &.active { + ul { + height: 0; + opacity: 0; + } + + svg { + transform: rotate(180deg); + } + } + + .header { + height: 2%; + width: 98%; + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; + margin: 0; + margin-left: 1%; + padding-top: 1%; + font-weight: bold; + } + + ul { + height: 100%; + margin: 0; + overflow: hidden; + opacity: 1; + transition: 0.4s ease; + } + + li { + list-style-type: none; + cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + + img { + height: $icon-size; + aspect-ratio: 1; + } + + p { + margin: 0; + } + } + + svg { + transition: 0.4s ease; + } + + .contact { + list-style-type: none; + margin: 0 auto; + text-align: left; + cursor: pointer + } +} diff --git a/src/components/contacts_window.rs b/src/components/contacts_window/contacts_window.rs similarity index 84% rename from src/components/contacts_window.rs rename to src/components/contacts_window/contacts_window.rs index 82303c9..8c57ee7 100644 --- a/src/components/contacts_window.rs +++ b/src/components/contacts_window/contacts_window.rs @@ -1,16 +1,14 @@ use dioxus::prelude::*; +use dioxus_std::utils::rw::UseRw; -use crate::app_settings::AppSettings; -use crate::components::contacts::Contacts; -use crate::components::header::Header; -use crate::components::user_infos::UserInfos; - -turf::style_sheet!("src/components/contacts_window.scss"); - -pub fn ContactWindow(cx: Scope) -> Element { - let app_context = use_shared_state::(cx).unwrap(); +use crate::base::Store; +use crate::components::contacts_window::contacts::Contacts; +use crate::components::contacts_window::user_infos::UserInfos; +turf::style_sheet!("src/components/contacts_window/contacts_window.scss"); +#[inline_props] +pub fn ContactsWindow<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { cx.render(rsx! { style { STYLE_SHEET }, @@ -28,7 +26,7 @@ pub fn ContactWindow(cx: Scope) -> Element { class: ClassName::USER_INFO, }, - UserInfos {}, + UserInfos {rw_store: rw_store}, }, div { @@ -86,7 +84,7 @@ pub fn ContactWindow(cx: Scope) -> Element { }, }, - Contacts {}, + Contacts {rw_store: rw_store}, div { class: ClassName::FOOTER, diff --git a/src/components/contacts_window.scss b/src/components/contacts_window/contacts_window.scss similarity index 97% rename from src/components/contacts_window.scss rename to src/components/contacts_window/contacts_window.scss index 51e5322..26a8865 100644 --- a/src/components/contacts_window.scss +++ b/src/components/contacts_window/contacts_window.scss @@ -1,4 +1,4 @@ -@import "../_base.scss"; +@import "../../_base.scss"; .contactsWindow { width: 100%; diff --git a/src/components/contacts_window/mod.rs b/src/components/contacts_window/mod.rs new file mode 100644 index 0000000..6a86f27 --- /dev/null +++ b/src/components/contacts_window/mod.rs @@ -0,0 +1,5 @@ +pub mod contacts_window; + +mod contacts; +mod contacts_section; +mod user_infos; diff --git a/src/components/user_infos.rs b/src/components/contacts_window/user_infos.rs similarity index 64% rename from src/components/user_infos.rs rename to src/components/contacts_window/user_infos.rs index d98cd39..6801520 100644 --- a/src/components/user_infos.rs +++ b/src/components/contacts_window/user_infos.rs @@ -1,10 +1,12 @@ use dioxus::prelude::*; use dioxus_free_icons::icons::md_navigation_icons::MdArrowDropDown; use dioxus_free_icons::Icon; +use dioxus_std::utils::rw::UseRw; +use crate::base::Store; use crate::components::avatar_selector::AvatarSelector; -turf::style_sheet!("src/components/user_infos.scss"); +turf::style_sheet!("src/components/contacts_window/user_infos.scss"); fn DownArrowIcon(cx: Scope) -> Element { cx.render(rsx! { @@ -17,7 +19,18 @@ fn DownArrowIcon(cx: Scope) -> Element { }) } -pub fn UserInfos(cx: Scope) -> Element { +static MESSAGE_PLACEHOLDER: &str = ""; + +#[inline_props] +pub fn UserInfos<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { + println!("UserInfos rendering"); + + let store = rw_store.read().unwrap().clone(); + + let user_id = store.user_id.unwrap(); + let user_info = store.user_infos.get(&user_id).unwrap(); + let user_display_name = user_info.display_name.as_ref().unwrap(); + cx.render(rsx! { style { STYLE_SHEET }, @@ -36,7 +49,7 @@ pub fn UserInfos(cx: Scope) -> Element { class: ClassName::USER_ID, p { class: ClassName::USER_NAME, - "SUPER USER" + "{user_display_name}", }, p { class: ClassName::USER_STATUS, @@ -48,7 +61,8 @@ pub fn UserInfos(cx: Scope) -> Element { div { class: ClassName::USER_MESSAGE, p { - "My message", + // TODO: Handle user message + MESSAGE_PLACEHOLDER, } DownArrowIcon {}, }, diff --git a/src/components/user_infos.scss b/src/components/contacts_window/user_infos.scss similarity index 98% rename from src/components/user_infos.scss rename to src/components/contacts_window/user_infos.scss index 0829d0e..ad6eb44 100644 --- a/src/components/user_infos.scss +++ b/src/components/contacts_window/user_infos.scss @@ -1,4 +1,4 @@ -@import "../_base.scss" +@import "../../_base.scss" .userInfo { position: relative; diff --git a/src/components/login.rs b/src/components/login.rs index 0b74f05..90f0714 100644 --- a/src/components/login.rs +++ b/src/components/login.rs @@ -11,19 +11,15 @@ use crate::matrix_client::{LoginStyle, MatrixClient}; turf::style_sheet!("src/components/login.scss"); -#[derive(Props)] -pub struct LoginProps<'a> { - pub store: &'a mut UseRw, -} +static EMPTY_PLACEHOLDER: &str = "Tmp placeholder"; -pub fn Login<'a>(cx: Scope<'a, LoginProps<'a>>) -> Element<'a> { +#[inline_props] +pub fn Login<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { let app_context = use_shared_state::(cx).unwrap(); let invalid_login = use_state(cx, || false); let login = use_ref(cx, || Login::new()); - let store = cx.props.store.clone(); - - let empty_placeholder = String::from(""); + let arc_store = Arc::new(rw_store.to_owned().clone()); let password_class = if **invalid_login { ClassName::INVALID_INPUT @@ -33,14 +29,15 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps<'a>>) -> Element<'a> { let run_matrix_client = move |_| { cx.spawn({ - to_owned![app_context, invalid_login, login, store]; + to_owned![app_context, invalid_login, login, arc_store]; - let homeserver_url = login.read().homeserver_url.clone().unwrap(); - let username = login.read().email.clone().unwrap(); - let password = login.read().password.clone().unwrap(); + let login_ref = login.read(); + let homeserver_url = login_ref.homeserver_url.clone().unwrap(); + let username = login_ref.email.clone().unwrap(); + let password = login_ref.password.clone().unwrap(); async move { - let requester = MatrixClient::spawn(homeserver_url, store).await; + let requester = MatrixClient::spawn(homeserver_url, arc_store.clone()).await; requester.init(); match requester.login(LoginStyle::Password(username, password)) { @@ -58,6 +55,12 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps<'a>>) -> Element<'a> { }); }; + let login_ref = login.read(); + let placeholder = EMPTY_PLACEHOLDER.to_string(); + let homeserver_url_value = login_ref.homeserver_url.as_ref().unwrap_or(&placeholder); + let email_value = login_ref.email.as_ref().unwrap_or(&placeholder); + let password_value = login_ref.password.as_ref().unwrap_or(&placeholder); + cx.render(rsx! { style { STYLE_SHEET }, @@ -83,7 +86,7 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps<'a>>) -> Element<'a> { id: "input-homeserver-url", r#type: "text", name: "homeserver URL", - value: "{(login.read().homeserver_url.as_ref().unwrap_or(&empty_placeholder))}", + value: "{homeserver_url_value}", oninput: move |evt| login.write().homeserver_url = Some(evt.value.clone()), }, @@ -94,7 +97,7 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps<'a>>) -> Element<'a> { id: "login-input-email", r#type: "text", name: "email", - value: "{login.read().email.as_ref().unwrap_or(&empty_placeholder)}", + value: "{email_value}", oninput: move |evt| login.write().email = Some(evt.value.clone()), }, p { @@ -105,7 +108,7 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps<'a>>) -> Element<'a> { id: "login-input-password", r#type: "password", name: "Password", - value: "{login.read().password.as_ref().unwrap_or(&empty_placeholder)}", + value: "{password_value}", oninput: move |evt| { login.write().password = Some(evt.value.clone()); invalid_login.set(false); diff --git a/src/components.rs b/src/components/mod.rs similarity index 68% rename from src/components.rs rename to src/components/mod.rs index 31b9630..3b346fc 100644 --- a/src/components.rs +++ b/src/components/mod.rs @@ -1,6 +1,4 @@ pub mod avatar_selector; -pub mod contacts; pub mod contacts_window; pub mod header; pub mod login; -pub mod user_infos; diff --git a/src/main.rs b/src/main.rs index 07947c8..e702e7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #![allow(non_snake_case)] + use dioxus::prelude::*; use dioxus_desktop::Config; use dioxus_std::utils::rw::use_rw; @@ -7,7 +8,7 @@ pub mod components; pub mod matrix_client; use crate::base::{AppSettings, Store}; -use crate::components::contacts_window::ContactWindow; +use crate::components::contacts_window::contacts_window::ContactsWindow; use crate::components::login::Login; mod base; @@ -15,16 +16,16 @@ mod base; fn App(cx: Scope) -> Element { use_shared_state_provider(cx, || cx.props.clone()); - let store = use_rw(cx, || Store::new()); + let rw_store = use_rw(cx, || Store::new()); - let is_logged = store.read().unwrap().is_logged; + let is_logged = rw_store.read().unwrap().is_logged; cx.render(rsx! { if is_logged { - rsx!(ContactWindow {}) + rsx!(ContactsWindow {rw_store: rw_store}) } else { - rsx!(Login {store: store}) + rsx!(Login {rw_store: rw_store}) } }) } diff --git a/src/matrix_client.rs b/src/matrix_client.rs index 294c9e9..5330232 100644 --- a/src/matrix_client.rs +++ b/src/matrix_client.rs @@ -1,18 +1,42 @@ use std::fmt::{Debug, Formatter}; use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; +use std::time::Duration; use dioxus_std::utils::rw::UseRw; -use matrix_sdk::{ - config::SyncSettings, - room::Room as MatrixRoom, - ruma::events::{presence::PresenceEvent, typing::SyncTypingEvent}, - Client, -}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; -use crate::base::Store; +use matrix_sdk::{ + config::SyncSettings, + event_handler::Ctx, + room::Room as MatrixRoom, + ruma::{ + events::{ + key::verification::{ + done::{OriginalSyncKeyVerificationDoneEvent, ToDeviceKeyVerificationDoneEvent}, + key::{OriginalSyncKeyVerificationKeyEvent, ToDeviceKeyVerificationKeyEvent}, + request::ToDeviceKeyVerificationRequestEvent, + start::{OriginalSyncKeyVerificationStartEvent, ToDeviceKeyVerificationStartEvent}, + }, + presence::PresenceEvent, + reaction::ReactionEventContent, + room::{ + member::{OriginalSyncRoomMemberEvent, RoomMemberEventContent}, + message::RoomMessageEventContent, + name::RoomNameEventContent, + redaction::OriginalSyncRoomRedactionEvent, + topic::RoomTopicEventContent, + }, + typing::SyncTypingEvent, + SyncMessageLikeEvent, SyncStateEvent, + }, + OwnedRoomId, OwnedUserId, + }, + Client, DisplayName, RoomMemberships, +}; + +use crate::base::{ByIdRooms, ReactiveStore, Room, Store, UserInfo}; #[derive(Debug)] pub enum LoginStyle { @@ -78,7 +102,7 @@ fn oneshot() -> (ClientReply, ClientResponse) { #[derive(Debug)] pub struct Requester { - pub client: Client, + pub client: Arc, pub tx: UnboundedSender, } @@ -106,35 +130,42 @@ impl Requester { pub struct MatrixClient { initialized: bool, - client: Option>, + client: Arc, // sync_token: Option, - // load_handle: Option>, + load_handle: Option>, sync_handle: Option>, - store: UseRw, + store: ReactiveStore, } impl MatrixClient { - pub async fn spawn(homeserver_url: String, store: UseRw) -> Requester { - let (tx, rx) = unbounded_channel::(); - - let client = Client::builder() - .homeserver_url(&homeserver_url) - .build() - .await - .unwrap(); - - let mut matrix_client = MatrixClient { - client: None, + fn new(store: ReactiveStore, client: Arc) -> Self { + Self { + client: client, initialized: false, // sync_token: None, - // load_handle: None, + load_handle: None, sync_handle: None, store: store, - }; - matrix_client.client = Some(Arc::new(client.clone())); + } + } - tokio::spawn(async move { - matrix_client.work(rx).await; + pub async fn spawn(homeserver_url: String, store: ReactiveStore) -> Requester { + let (tx, rx) = unbounded_channel::(); + + let client = Arc::new( + Client::builder() + .homeserver_url(&homeserver_url) + .build() + .await + .unwrap(), + ); + + let mut matrix_client = MatrixClient::new(store, client.clone()); + + tokio::spawn({ + async move { + matrix_client.work(rx).await; + } }); Requester { client, tx } @@ -176,27 +207,289 @@ impl MatrixClient { } } - async fn on_sync_event(_ev: SyncTypingEvent, room: MatrixRoom) { - println!("== on_sync_event =="); + async fn on_sync_typing_event( + _ev: SyncTypingEvent, + room: MatrixRoom, + _store: Ctx, + ) { + println!("== on_sync_typing_event =="); let room_id = room.room_id().to_owned(); - dbg!(room_id); + // dbg!(room_id); } - async fn on_presence_event(_ev: PresenceEvent) { + async fn on_presence_event(_ev: PresenceEvent, _store: Ctx) { println!("== on_presence_event =="); + dbg!(_ev); + } + + async fn on_sync_state_event( + _ev: SyncStateEvent, + _room: MatrixRoom, + _store: Ctx, + ) { + println!("== on_sync_state_event =="); + dbg!(_ev); + } + + async fn on_room_topic_event( + ev: SyncStateEvent, + room: MatrixRoom, + reactive_store: Ctx, + ) { + dbg!(&ev); + if let SyncStateEvent::Original(ev) = ev { + let room_id = room.room_id().to_owned(); + let store = reactive_store.read().unwrap().to_owned(); + + if let Some(store_room) = store.rooms.get(&room_id) { + store_room.write().unwrap().topic = Some(ev.content.topic); + let _ = reactive_store.write(store); + println!("HOP"); + } else { + println!("No room with \"{room_id}\" id known"); + } + } + } + + async fn on_room_member_event( + ev: SyncStateEvent, + room: MatrixRoom, + store_ctx: Ctx, + ) { + println!("== on_room_member_event =="); + // // dbg!(ev); + // // dbg!(room); + // if room.invited_members_count() > 0 { + // dbg!(room); + // } + // if let SyncStateEvent::Original(ev) = ev {} + } + + async fn on_sync_message_like_room_message_event( + _ev: SyncMessageLikeEvent, + _room: MatrixRoom, + _client: Client, + _store: Ctx, + ) { + println!("== on_sync_message_like_room_message_event =="); + // dbg!(_ev); + } + + async fn on_sync_message_like_reaction_event( + _ev: SyncMessageLikeEvent, + _room: MatrixRoom, + _store: Ctx, + ) { + println!("== on_sync_message_like_reaction_event =="); + } + + async fn on_original_sync_room_redaction_event( + _ev: OriginalSyncRoomRedactionEvent, + _room: MatrixRoom, + _store: Ctx, + ) { + println!("== on_original_sync_room_redaction_event =="); + } + + async fn on_original_sync_room_member_event( + _ev: OriginalSyncRoomMemberEvent, + room: MatrixRoom, + _client: Client, + store_ctx: Ctx, + ) { + println!("== on_original_sync_room_member_event =="); + let room_id = room.room_id(); + dbg!(room_id); + + let mut store = store_ctx.read().unwrap().to_owned(); + dbg!(store.rooms.keys()); + let is_direct = room.is_direct().await.ok(); + store.rooms.insert( + OwnedRoomId::from(room_id), + Arc::new(RwLock::new(Room::new(Arc::new(room), None, is_direct))), + ); + let _ = store_ctx.write(store); + } + + async fn on_original_sync_key_verif_start_event( + _ev: OriginalSyncKeyVerificationStartEvent, + _client: Client, + _store: Ctx, + ) { + println!("== on_original_sync_key_verif_start_event =="); + } + + async fn on_original_sync_key_verif_key_event( + _ev: OriginalSyncKeyVerificationKeyEvent, + _client: Client, + _store: Ctx, + ) { + println!("== on_original_sync_key_verif_key_event =="); + } + + async fn on_original_sync_key_verif_done_event( + _ev: OriginalSyncKeyVerificationDoneEvent, + _client: Client, + _store: Ctx, + ) { + println!("== on_original_sync_key_verif_done_event =="); + } + + async fn on_device_key_verif_req_event( + _ev: ToDeviceKeyVerificationRequestEvent, + _client: Client, + _store: Ctx, + ) { + println!("== on_device_key_verif_req_event =="); + } + + async fn on_device_key_verif_start_event( + _ev: ToDeviceKeyVerificationStartEvent, + _client: Client, + _store: Ctx, + ) { + println!("== on_device_key_verif_start_event =="); + } + + async fn on_device_key_verif_key_event( + _ev: ToDeviceKeyVerificationKeyEvent, + _client: Client, + _store: Ctx, + ) { + println!("== on_device_key_verif_key_event =="); + } + + async fn on_device_key_verif_done_event( + _ev: ToDeviceKeyVerificationDoneEvent, + _client: Client, + _store: Ctx, + ) { + println!("== on_device_key_verif_done_event =="); } async fn init(&mut self) { - self.initialized = true; + let client = self.client.clone(); + let store = self.store.clone(); + client.add_event_handler_context(store); - let client = self.client.clone().unwrap(); - - let _ = client.add_event_handler(MatrixClient::on_sync_event); + let _ = client.add_event_handler(MatrixClient::on_sync_typing_event); let _ = client.add_event_handler(MatrixClient::on_presence_event); + let _ = client.add_event_handler(MatrixClient::on_sync_state_event); + let _ = client.add_event_handler(MatrixClient::on_sync_message_like_room_message_event); + let _ = client.add_event_handler(MatrixClient::on_sync_message_like_reaction_event); + let _ = client.add_event_handler(MatrixClient::on_original_sync_room_redaction_event); + let _ = client.add_event_handler(MatrixClient::on_original_sync_room_member_event); + let _ = client.add_event_handler(MatrixClient::on_original_sync_key_verif_start_event); + let _ = client.add_event_handler(MatrixClient::on_original_sync_key_verif_key_event); + let _ = client.add_event_handler(MatrixClient::on_original_sync_key_verif_done_event); + let _ = client.add_event_handler(MatrixClient::on_device_key_verif_req_event); + let _ = client.add_event_handler(MatrixClient::on_device_key_verif_start_event); + let _ = client.add_event_handler(MatrixClient::on_device_key_verif_key_event); + let _ = client.add_event_handler(MatrixClient::on_device_key_verif_done_event); + let _ = client.add_event_handler(MatrixClient::on_room_topic_event); + let _ = client.add_event_handler(MatrixClient::on_room_member_event); + + self.load_handle = tokio::spawn({ + let client = self.client.clone(); + let store = self.store.clone(); + + async move { + let rooms_refresh = Self::refresh_rooms_forever(client.as_ref(), store.as_ref()); + let ((),) = tokio::join!(rooms_refresh); + } + }) + .into(); + + self.initialized = true; + } + + async fn load_user_infos(&self) { + let mut store = self.store.read().unwrap().to_owned(); + + let user_id = self.client.user_id().unwrap(); + store.user_id = Some(OwnedUserId::from(user_id)); + + let res = self.client.account().get_profile().await.unwrap(); + let user_info = UserInfo::new(res.avatar_url, res.displayname, res.blurhash); + + store + .user_infos + .insert(OwnedUserId::from(user_id), user_info); + + let _ = self.store.write(store); + } + + async fn get_formatted_room_name(room: &MatrixRoom) -> String { + room.display_name() + .await + .unwrap_or(DisplayName::Empty) + .to_string() + } + + async fn refresh_rooms(client: &Client, store: &UseRw) { + // println!("== refresh_rooms =="); + + let mut _store = store.read().unwrap().to_owned(); + + let mut new_rooms = ByIdRooms::new(); + + for room in &client.joined_rooms() { + let room_id = room.room_id(); + + let members = room.members(RoomMemberships::empty()).await.unwrap(); + if !_store.rooms.contains_key(room_id) { + let is_direct = room.is_direct().await.ok(); + new_rooms.insert( + OwnedRoomId::from(room_id), + Arc::new(RwLock::new(Room::new( + Arc::new(room.to_owned()), + None, + is_direct, + ))), + ); + } + } + + for room in client.invited_rooms().into_iter() { + let room_id = room.room_id(); + + if !_store.rooms.contains_key(room_id) { + let is_direct = room.is_direct().await.ok(); + new_rooms.insert( + OwnedRoomId::from(room_id), + Arc::new(RwLock::new(Room::new( + Arc::new(room.to_owned()), + None, + is_direct, + ))), + ); + } + } + + let mut updated = false; + for (room_id, room) in new_rooms.into_iter() { + updated = true; + _store.rooms.insert(room_id, room); + } + + if updated { + let _ = store.write(_store); + } + } + + async fn refresh_rooms_forever(client: &Client, store: &UseRw) { + // TODO: Add interval to config + let mut interval = tokio::time::interval(Duration::from_secs(5)); + + loop { + Self::refresh_rooms(client, store).await; + + interval.tick().await; + } } async fn login_and_sync(&mut self, style: LoginStyle) -> anyhow::Result<()> { - let client = self.client.clone().unwrap(); + let client = self.client.clone(); match style { LoginStyle::Password(username, password) => { @@ -210,22 +503,24 @@ impl MatrixClient { } } - self.sync_handle = tokio::spawn(async move { - // Sync once so we receive the client state and old messages - let _ret = client.sync_once(SyncSettings::default()).await; + self.load_user_infos().await; - let _rooms = client.rooms(); + self.sync_handle = tokio::spawn({ + async move { + // Sync once so we receive the client state and old messages + let _ = client.sync_once(SyncSettings::default()).await; - loop { - let settings = SyncSettings::default(); - let _ = client.sync(settings).await; + loop { + let settings = SyncSettings::default(); + let _ = client.sync(settings).await; + } } }) .into(); let mut store = self.store.read().unwrap().to_owned(); store.is_logged = true; - let _ = self.store.write(store).unwrap(); + let _ = self.store.write(store); println!("User connected to the homeserver"); Ok(())