🎨 Reorganize the contacts_window widgets + add first interactions with homeserver
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
$font-size: 100vh * 0.01;
|
||||
$icon-size: $font-size * 2;
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
110
src/base.rs
110
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<OwnedMxcUri>,
|
||||
pub display_name: Option<String>,
|
||||
pub blurhash: Option<String>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn new() -> Self {
|
||||
Self { is_logged: false }
|
||||
impl UserInfo {
|
||||
pub fn new(
|
||||
avatar_url: Option<OwnedMxcUri>,
|
||||
display_name: Option<String>,
|
||||
blurhash: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
avatar_url,
|
||||
display_name,
|
||||
blurhash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Room {
|
||||
pub matrix_room: Arc<MatrixRoom>,
|
||||
pub topic: Option<String>,
|
||||
pub members: HashMap<OwnedUserId, RoomMember>,
|
||||
pub is_direct: Option<bool>,
|
||||
}
|
||||
|
||||
impl Room {
|
||||
pub fn new(
|
||||
matrix_room: Arc<MatrixRoom>,
|
||||
topic: Option<String>,
|
||||
is_direct: Option<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
matrix_room,
|
||||
topic,
|
||||
members: HashMap::new(),
|
||||
is_direct,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<String> {
|
||||
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<OwnedRoomId, Arc<RwLock<Room>>>;
|
||||
pub type ByIdUserInfos = HashMap<OwnedUserId, UserInfo>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Store {
|
||||
pub is_logged: bool,
|
||||
pub rooms: ByIdRooms,
|
||||
pub user_infos: ByIdUserInfos,
|
||||
pub user_id: Option<OwnedUserId>,
|
||||
}
|
||||
|
||||
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<UseRw<Store>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppSettings {
|
||||
pub requester: Option<Arc<Requester>>,
|
||||
|
@@ -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",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
47
src/components/contacts_window/contacts.rs
Normal file
47
src/components/contacts_window/contacts.rs
Normal file
@@ -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<Store>) -> Element {
|
||||
println!("Contacts rendering");
|
||||
|
||||
let store = rw_store.read().unwrap();
|
||||
|
||||
let rooms = &store.rooms;
|
||||
|
||||
let rooms_len = rooms.len();
|
||||
let mut groups = Vec::<Room>::with_capacity(rooms_len);
|
||||
let mut directs = Vec::<Room>::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)},
|
||||
},
|
||||
})
|
||||
}
|
6
src/components/contacts_window/contacts.scss
Normal file
6
src/components/contacts_window/contacts.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
@import "../../_base.scss"
|
||||
|
||||
.contacts {
|
||||
height: 72%;
|
||||
background-color: white;
|
||||
}
|
87
src/components/contacts_window/contacts_section.rs
Normal file
87
src/components/contacts_window/contacts_section.rs
Normal file
@@ -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<Vec<Room>>) -> 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(),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
67
src/components/contacts_window/contacts_section.scss
Normal file
67
src/components/contacts_window/contacts_section.scss
Normal file
@@ -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
|
||||
}
|
||||
}
|
@@ -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::<AppSettings>(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<Store>) -> 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,
|
@@ -1,4 +1,4 @@
|
||||
@import "../_base.scss";
|
||||
@import "../../_base.scss";
|
||||
|
||||
.contactsWindow {
|
||||
width: 100%;
|
5
src/components/contacts_window/mod.rs
Normal file
5
src/components/contacts_window/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod contacts_window;
|
||||
|
||||
mod contacts;
|
||||
mod contacts_section;
|
||||
mod user_infos;
|
@@ -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 = "<Enter a personal message>";
|
||||
|
||||
#[inline_props]
|
||||
pub fn UserInfos<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> 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 {},
|
||||
},
|
@@ -1,4 +1,4 @@
|
||||
@import "../_base.scss"
|
||||
@import "../../_base.scss"
|
||||
|
||||
.userInfo {
|
||||
position: relative;
|
@@ -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<Store>,
|
||||
}
|
||||
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<Store>) -> Element {
|
||||
let app_context = use_shared_state::<AppSettings>(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);
|
||||
|
@@ -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;
|
11
src/main.rs
11
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<AppSettings>) -> 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})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -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<T>() -> (ClientReply<T>, ClientResponse<T>) {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Requester {
|
||||
pub client: Client,
|
||||
pub client: Arc<Client>,
|
||||
pub tx: UnboundedSender<WorkerTask>,
|
||||
}
|
||||
|
||||
@@ -106,35 +130,42 @@ impl Requester {
|
||||
|
||||
pub struct MatrixClient {
|
||||
initialized: bool,
|
||||
client: Option<Arc<Client>>,
|
||||
client: Arc<Client>,
|
||||
// sync_token: Option<String>,
|
||||
// load_handle: Option<JoinHandle<()>>,
|
||||
load_handle: Option<JoinHandle<()>>,
|
||||
sync_handle: Option<JoinHandle<()>>,
|
||||
store: UseRw<Store>,
|
||||
store: ReactiveStore,
|
||||
}
|
||||
|
||||
impl MatrixClient {
|
||||
pub async fn spawn(homeserver_url: String, store: UseRw<Store>) -> Requester {
|
||||
let (tx, rx) = unbounded_channel::<WorkerTask>();
|
||||
|
||||
let client = Client::builder()
|
||||
.homeserver_url(&homeserver_url)
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut matrix_client = MatrixClient {
|
||||
client: None,
|
||||
fn new(store: ReactiveStore, client: Arc<Client>) -> 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::<WorkerTask>();
|
||||
|
||||
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<ReactiveStore>,
|
||||
) {
|
||||
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<ReactiveStore>) {
|
||||
println!("== on_presence_event ==");
|
||||
dbg!(_ev);
|
||||
}
|
||||
|
||||
async fn on_sync_state_event(
|
||||
_ev: SyncStateEvent<RoomNameEventContent>,
|
||||
_room: MatrixRoom,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_sync_state_event ==");
|
||||
dbg!(_ev);
|
||||
}
|
||||
|
||||
async fn on_room_topic_event(
|
||||
ev: SyncStateEvent<RoomTopicEventContent>,
|
||||
room: MatrixRoom,
|
||||
reactive_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
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<RoomMemberEventContent>,
|
||||
room: MatrixRoom,
|
||||
store_ctx: Ctx<ReactiveStore>,
|
||||
) {
|
||||
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<RoomMessageEventContent>,
|
||||
_room: MatrixRoom,
|
||||
_client: Client,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_sync_message_like_room_message_event ==");
|
||||
// dbg!(_ev);
|
||||
}
|
||||
|
||||
async fn on_sync_message_like_reaction_event(
|
||||
_ev: SyncMessageLikeEvent<ReactionEventContent>,
|
||||
_room: MatrixRoom,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_sync_message_like_reaction_event ==");
|
||||
}
|
||||
|
||||
async fn on_original_sync_room_redaction_event(
|
||||
_ev: OriginalSyncRoomRedactionEvent,
|
||||
_room: MatrixRoom,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_original_sync_room_redaction_event ==");
|
||||
}
|
||||
|
||||
async fn on_original_sync_room_member_event(
|
||||
_ev: OriginalSyncRoomMemberEvent,
|
||||
room: MatrixRoom,
|
||||
_client: Client,
|
||||
store_ctx: Ctx<ReactiveStore>,
|
||||
) {
|
||||
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<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_original_sync_key_verif_start_event ==");
|
||||
}
|
||||
|
||||
async fn on_original_sync_key_verif_key_event(
|
||||
_ev: OriginalSyncKeyVerificationKeyEvent,
|
||||
_client: Client,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_original_sync_key_verif_key_event ==");
|
||||
}
|
||||
|
||||
async fn on_original_sync_key_verif_done_event(
|
||||
_ev: OriginalSyncKeyVerificationDoneEvent,
|
||||
_client: Client,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_original_sync_key_verif_done_event ==");
|
||||
}
|
||||
|
||||
async fn on_device_key_verif_req_event(
|
||||
_ev: ToDeviceKeyVerificationRequestEvent,
|
||||
_client: Client,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_device_key_verif_req_event ==");
|
||||
}
|
||||
|
||||
async fn on_device_key_verif_start_event(
|
||||
_ev: ToDeviceKeyVerificationStartEvent,
|
||||
_client: Client,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_device_key_verif_start_event ==");
|
||||
}
|
||||
|
||||
async fn on_device_key_verif_key_event(
|
||||
_ev: ToDeviceKeyVerificationKeyEvent,
|
||||
_client: Client,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
println!("== on_device_key_verif_key_event ==");
|
||||
}
|
||||
|
||||
async fn on_device_key_verif_done_event(
|
||||
_ev: ToDeviceKeyVerificationDoneEvent,
|
||||
_client: Client,
|
||||
_store: Ctx<ReactiveStore>,
|
||||
) {
|
||||
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<Store>) {
|
||||
// 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<Store>) {
|
||||
// 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(())
|
||||
|
Reference in New Issue
Block a user