🎨 Reorganize the contacts_window widgets + add first interactions with homeserver

This commit is contained in:
2023-08-15 22:05:26 +02:00
parent 22ef914304
commit 2159c6adeb
17 changed files with 712 additions and 243 deletions

View File

@@ -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",
}
},
},
},
})
}

View File

@@ -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
}
}

View 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)},
},
})
}

View File

@@ -0,0 +1,6 @@
@import "../../_base.scss"
.contacts {
height: 72%;
background-color: white;
}

View 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(),
},
},
})
}

View 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
}
}

View File

@@ -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,

View File

@@ -1,4 +1,4 @@
@import "../_base.scss";
@import "../../_base.scss";
.contactsWindow {
width: 100%;

View File

@@ -0,0 +1,5 @@
pub mod contacts_window;
mod contacts;
mod contacts_section;
mod user_infos;

View File

@@ -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 {},
},

View File

@@ -1,4 +1,4 @@
@import "../_base.scss"
@import "../../_base.scss"
.userInfo {
position: relative;

View File

@@ -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);

4
src/components/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod avatar_selector;
pub mod contacts_window;
pub mod header;
pub mod login;