From 3afed02aa802feca269bfb026d19d5d4aaaa47fc Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 26 Apr 2024 19:23:34 +0200 Subject: [PATCH 01/41] =?UTF-8?q?=F0=9F=9A=A7=20Make=20Button=20usable=20o?= =?UTF-8?q?utside=20of=20the=20button.rs=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/button.rs | 2 +- src/ui/components/button.scss | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ui/components/button.rs b/src/ui/components/button.rs index 1c044de..225b29d 100644 --- a/src/ui/components/button.rs +++ b/src/ui/components/button.rs @@ -77,7 +77,7 @@ pub struct ButtonProps { children: Element, } -fn Button(props: ButtonProps) -> Element { +pub fn Button(props: ButtonProps) -> Element { rsx! { style { {STYLE_SHEET} }, diff --git a/src/ui/components/button.scss b/src/ui/components/button.scss index 4476bf9..e16ecd6 100644 --- a/src/ui/components/button.scss +++ b/src/ui/components/button.scss @@ -5,16 +5,22 @@ aspect-ratio: 3.5; border: $border-normal; - border-radius: $border-radius; + border-radius: 5%; color: get-color(greyscale, 0); font-family: "Geist"; font-weight: bold; + // To center the inner svg + // TODO: Find a more efficient way to center + display: flex; + align-items: center; + justify-content: center; + svg { - height: 100%; - width: 100%; + height: 85%; + width: 85%; text { font-size: 50; From 894f32e177e9c4a0d56c41b5e3a96a3ebba5464f Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 26 Apr 2024 19:27:11 +0200 Subject: [PATCH 02/41] =?UTF-8?q?=F0=9F=92=84=20Adjust=20Login=20padding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/_panel.scss | 10 ++++------ src/ui/components/login.scss | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ui/components/_panel.scss b/src/ui/components/_panel.scss index 4c18199..ef24da3 100644 --- a/src/ui/components/_panel.scss +++ b/src/ui/components/_panel.scss @@ -1,14 +1,12 @@ @import "../base.scss"; $panel-aspect-ratio: 1/1.618; -$panel-padding-v: 5%; -$panel-padding-h: 5%; -%panel { - padding: $panel-padding-v $panel-padding-h; +@mixin panel($padding-v: 2%, $padding-h: 2%) { + padding: $padding-v $padding-h; - height: calc(100% - $panel-padding-v - (2 * $border-big-width)); - width: calc(100% - $panel-padding-h - (2 * $border-big-width)); + height: calc(100% - (2 * $padding-v) - (2 * $border-big-width)); + width: calc(100% - (2 * $padding-h) - (2 * $border-big-width)); flex-shrink: 0; border: $border-big; diff --git a/src/ui/components/login.scss b/src/ui/components/login.scss index 59692c4..0c23b7e 100644 --- a/src/ui/components/login.scss +++ b/src/ui/components/login.scss @@ -3,7 +3,8 @@ @import "./spinner.scss"; .login { - @extend %panel; + $padding: 5%; + @include panel($padding, $padding); $aspect-ratio: var(--aspect-ratio); From 7078f86cd846b738bbeb4e8ded5bbc5f63a87326 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 26 Apr 2024 19:31:05 +0200 Subject: [PATCH 03/41] =?UTF-8?q?=F0=9F=92=84=20Make=20the=20"logo"=20shap?= =?UTF-8?q?e=20reusable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/icons.rs | 24 ++++++++++++++++++++++-- src/ui/components/spinner.rs | 26 ++++---------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/ui/components/icons.rs b/src/ui/components/icons.rs index d15a782..c79de79 100644 --- a/src/ui/components/icons.rs +++ b/src/ui/components/icons.rs @@ -10,12 +10,32 @@ include!(concat!(env!("OUT_DIR"), "/style_vars.rs")); use style::{COLOR_PRIMARY_100, COLOR_TERNARY_100}; pub fn DownArrowIcon() -> Element { +#[derive(Clone, PartialEq)] +pub(crate) struct LogoShape; +impl IconShape for LogoShape { + fn view_box(&self) -> &str { + "0 0 184 94" + } + fn xmlns(&self) -> &str { + "http://www.w3.org/2000/svg" + } + fn child_elements(&self) -> Element { + rsx! { + path { + "stroke-linejoin": "round", + "stroke-width": "6", + d: "M121.208 2 2 57.011l70.927-.265L61.363 92 182 36.724h-69.498L121.208 2Z" + } + } + } +} + +pub fn LogoIcon() -> Element { rsx! { style { {STYLE_SHEET} }, Icon { - class: ClassName::DOWN_ARROW_ICON, - icon: MdArrowDropDown, + icon: LogoShape, } } } diff --git a/src/ui/components/spinner.rs b/src/ui/components/spinner.rs index 91ef65c..68750b1 100644 --- a/src/ui/components/spinner.rs +++ b/src/ui/components/spinner.rs @@ -1,28 +1,10 @@ use dioxus::prelude::*; -use dioxus_free_icons::{Icon, IconShape}; +use dioxus_free_icons::Icon; + +use crate::ui::components::icons::LogoShape; turf::style_sheet!("src/ui/components/spinner.scss"); -#[derive(Clone, PartialEq)] -struct _Spinner; -impl IconShape for _Spinner { - fn view_box(&self) -> &str { - "0 0 184 94" - } - fn xmlns(&self) -> &str { - "http://www.w3.org/2000/svg" - } - fn child_elements(&self) -> Element { - rsx! { - path { - "stroke-linejoin": "round", - "stroke-width": "6", - d: "M121.208 2 2 57.011l70.927-.265L61.363 92 182 36.724h-69.498L121.208 2Z" - } - } - } -} - #[derive(PartialEq, Clone, Props)] pub struct SpinnerProps { #[props(default = true)] @@ -38,7 +20,7 @@ pub fn Spinner(props: SpinnerProps) -> Element { Icon { class: if props.animate { "" } else { ClassName::PAUSED }, - icon: _Spinner, + icon: LogoShape, } } } From f79ebb0b036a5b635f94f51abfa829f01c3e6888 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 26 Apr 2024 19:42:49 +0200 Subject: [PATCH 04/41] =?UTF-8?q?=E2=9C=A8=20Add=20SearchIcon,=20SpacesIco?= =?UTF-8?q?n,=20ChatsIcon=20and=20RoomsIcon=20elements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- src/ui/components/icons.rs | 26 +++++++++++++++++++++++++- src/ui/components/icons.scss | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ded1960..452401f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ web = ["dioxus/web"] [dependencies] dioxus = "0.5.*" -dioxus-free-icons = { version = "0.8", features = ["material-design-icons-navigation", "ionicons"] } dioxus-sdk = { version = "0.5.*", features = ["utils"] } +dioxus-free-icons = { version = "0.8.*", features = ["ionicons", "font-awesome-solid", "material-design-icons-navigation"] } # matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", branch = "main", default-features = false, features = ["js", "rustls-tls"] } matrix-sdk = { version = "0.7.*", default-features = false, features = ["js", "rustls-tls"] } diff --git a/src/ui/components/icons.rs b/src/ui/components/icons.rs index c79de79..e978c23 100644 --- a/src/ui/components/icons.rs +++ b/src/ui/components/icons.rs @@ -1,5 +1,8 @@ use const_format::formatcp; use dioxus::prelude::*; +use dioxus_free_icons::icons::fa_solid_icons::{ + FaComments, FaLayerGroup, FaMagnifyingGlass, FaPeopleGroup, +}; use dioxus_free_icons::icons::md_navigation_icons::MdArrowDropDown; use dioxus_free_icons::{Icon, IconShape}; @@ -9,7 +12,28 @@ include!(concat!(env!("OUT_DIR"), "/style_vars.rs")); use style::{COLOR_PRIMARY_100, COLOR_TERNARY_100}; -pub fn DownArrowIcon() -> Element { +macro_rules! transparent_icon { + ($name:ident, $icon:ident) => { + pub fn $name() -> Element { + rsx! { + style { {STYLE_SHEET} }, + Icon { + class: ClassName::TRANSPARENT_ICON, + icon: $icon, + } + } + } + }; +} + +// TODO: Remove this icon once the conversation panel finished +transparent_icon!(DownArrowIcon, MdArrowDropDown); + +transparent_icon!(SearchIcon, FaMagnifyingGlass); +transparent_icon!(SpacesIcon, FaLayerGroup); +transparent_icon!(ChatsIcon, FaComments); +transparent_icon!(RoomsIcon, FaPeopleGroup); + #[derive(Clone, PartialEq)] pub(crate) struct LogoShape; impl IconShape for LogoShape { diff --git a/src/ui/components/icons.scss b/src/ui/components/icons.scss index 494370c..5faeb01 100644 --- a/src/ui/components/icons.scss +++ b/src/ui/components/icons.scss @@ -1,4 +1,4 @@ -.down-arrow-icon { +.transparent-icon { color: transparent; path:last-child { From c4dcb0f87d4475670045bcec8ece4453ec8c0430 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 5 May 2024 21:43:41 +0200 Subject: [PATCH 05/41] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20ChatsWindow=20and?= =?UTF-8?q?=20ContactsWindow=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/images/add_user.png | Bin 698 -> 0 bytes public/images/add_user2.png | Bin 1055 -> 0 bytes public/images/aerobutton_border_down.png | Bin 1948 -> 0 bytes public/images/ban_user.png | Bin 1104 -> 0 bytes public/images/brush.png | Bin 494 -> 0 bytes public/images/button_border.png | Bin 1820 -> 0 bytes public/images/button_border_disabled.png | Bin 221 -> 0 bytes public/images/button_border_focus.png | Bin 223 -> 0 bytes public/images/directory.png | Bin 755 -> 0 bytes public/images/games.png | Bin 864 -> 0 bytes public/images/letter.png | Bin 306 -> 0 bytes public/images/medias.png | Bin 853 -> 0 bytes public/images/news.png | Bin 430 -> 0 bytes public/images/phone.png | Bin 639 -> 0 bytes public/images/settings.png | Bin 388 -> 0 bytes public/images/status_away.png | Bin 6715 -> 0 bytes public/images/status_busy.png | Bin 6611 -> 0 bytes public/images/status_online.png | Bin 5743 -> 0 bytes public/images/tbc_transfert.png | Bin 323 -> 0 bytes public/images/webcam.svg | 1 - public/images/wizz.png.old | Bin 719 -> 0 bytes .../components/chats_window/chats_window.scss | 94 ---------- .../components/chats_window/conversation.rs | 87 --------- .../components/chats_window/conversation.scss | 113 ------------ .../components/chats_window/edit_section.rs | 52 ------ .../components/chats_window/edit_section.scss | 55 ------ src/ui/components/chats_window/interface.rs | 39 ---- src/ui/components/chats_window/mod.rs | 171 ------------------ src/ui/components/chats_window/navbar.rs | 50 ----- src/ui/components/chats_window/navbar.scss | 26 --- src/ui/components/contacts_window/contacts.rs | 26 --- .../components/contacts_window/contacts.scss | 6 - .../contacts_window/contacts_section.rs | 149 --------------- .../contacts_window/contacts_section.scss | 67 ------- .../contacts_window/contacts_window.scss | 84 --------- src/ui/components/contacts_window/mod.rs | 98 ---------- .../components/contacts_window/user_infos.rs | 76 -------- .../contacts_window/user_infos.scss | 63 ------- 38 files changed, 1257 deletions(-) delete mode 100644 public/images/add_user.png delete mode 100644 public/images/add_user2.png delete mode 100644 public/images/aerobutton_border_down.png delete mode 100644 public/images/ban_user.png delete mode 100644 public/images/brush.png delete mode 100644 public/images/button_border.png delete mode 100644 public/images/button_border_disabled.png delete mode 100644 public/images/button_border_focus.png delete mode 100644 public/images/directory.png delete mode 100644 public/images/games.png delete mode 100644 public/images/letter.png delete mode 100644 public/images/medias.png delete mode 100644 public/images/news.png delete mode 100644 public/images/phone.png delete mode 100644 public/images/settings.png delete mode 100644 public/images/status_away.png delete mode 100644 public/images/status_busy.png delete mode 100644 public/images/status_online.png delete mode 100644 public/images/tbc_transfert.png delete mode 100644 public/images/webcam.svg delete mode 100644 public/images/wizz.png.old delete mode 100644 src/ui/components/chats_window/chats_window.scss delete mode 100644 src/ui/components/chats_window/conversation.rs delete mode 100644 src/ui/components/chats_window/conversation.scss delete mode 100644 src/ui/components/chats_window/edit_section.rs delete mode 100644 src/ui/components/chats_window/edit_section.scss delete mode 100644 src/ui/components/chats_window/interface.rs delete mode 100644 src/ui/components/chats_window/mod.rs delete mode 100644 src/ui/components/chats_window/navbar.rs delete mode 100644 src/ui/components/chats_window/navbar.scss delete mode 100644 src/ui/components/contacts_window/contacts.rs delete mode 100644 src/ui/components/contacts_window/contacts.scss delete mode 100644 src/ui/components/contacts_window/contacts_section.rs delete mode 100644 src/ui/components/contacts_window/contacts_section.scss delete mode 100644 src/ui/components/contacts_window/contacts_window.scss delete mode 100644 src/ui/components/contacts_window/mod.rs delete mode 100644 src/ui/components/contacts_window/user_infos.rs delete mode 100644 src/ui/components/contacts_window/user_infos.scss diff --git a/public/images/add_user.png b/public/images/add_user.png deleted file mode 100644 index f13708a68cad30e6bad8155f922b4e6a8597eb24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 698 zcmV;r0!96aP)5r00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0#8XqK~y+TjZ!~o z6Hyp{W4DG*p$I7=7^-L?fkvua1{-2&G)wRXu_42GQlytEdZEJ|NZ}BnZ}WnvmxAWV zl7m7$$j~be9un~lLT|jyxAl#;eZG6m(5cN2KEC(yzVG+_Md$Hke^oYqZ<6ifCcV46 zio$Y6#VAD$-G zmy%&>@-{RG!axMjghJMt5%WpyR4~Th(Q@ae-}I{daO$FWlar(?OttQ65R(|1d^34B z3u;&gIj9OEpZYM^ADp6VjjX%!W|38Ki3g??=)81OXv^0j;sh44MEP=176dY{nDC43 zfA0TF$FP8Ms62an)?9lh15)9HK= zgFJ@}lF&jyDdba~+O1Yg9goM1@gOhuMCODg(Cv1A7K_E{j%;07uYbhYANGgzU~mxe gTfmYe@dOC{2a`&ReU@?%?EnA(07*qoM6N<$f|}kuZU6uP diff --git a/public/images/add_user2.png b/public/images/add_user2.png deleted file mode 100644 index 82eea876f94ae4ecc0d1ad79f736ebaf880c5424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1055 zcmV+)1mOFLP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1GGs*K~zXf?Nm=_ z6jvC3#~g~?DAEXmYf!pKiGgkEdfGwJPNlSKVdEAPvUO!04eBcX8R=o>(9pF~n3NtS z80xg9Fa$z}TH;hI&LP-=D$GG)E+KOed>6@_;@&>L@6BvnZPO;=u|N3n=Dm6E_kHhs zzxUYdK-090rd^Y+&(1cc-FpV{@}#4bHLV|icO|o43n)B(y>V;qLGbwTQ_gt|aU26j zhWLHs(fs1r2|;3K=G9OFC6t%@6o7&SxYJYm&!7bt2wb=6ZK zhyzjIAojorbU>1}aB8a|HuX+a~QIz&T#T;nNG>97|=F(Y5Q+R<|=ih^@s9 zs9Flxekd{{0a0&cJM(s5B!KJ2CZ_&mtnyB(n?hrm_fBnft9A*j$N(R+cew**q(j54 zfUH;G`0?vhVIPRpKIhBsaf+&J7QGEOM1G`eym$A9F&XA1)G`y-PjTtpT_Zvxo z@eiWDp?#H_cGZ}ex#>TgpO2T8mRh&(Ka8hu{1IOHX~y~F$Ylj(GKtzhq0Vvx=eBqy zQvwx(G=m1V3e;vkdzM#wFQ&5ynS(=$qfOolwiHJsRxD<5Y*m-6t8iwAQBZTBI3*0A zV)j+|=sRga`XXCQ=OoyaFR`+?y{iK~0riQ`t@4i_#}!K!0TibK6|^ax%h9RB3&ZD! z52e%BjIP?ZHN%cf^sxsXckzI2a3Tw7i!tK(Tyt^o?BLp;g1S~OHSsXrmIGewqJxMY zTGAJ7LKmFag8I}DT9CbB!GA}lcD3Rfkt$qc@}}^L7c-yj4Sz7t6c@^?W zxj;47(J|zjW~hRJLP?+`APE~dPXEcmy$5@LNtgdO^7Xu0vLxXavL^YS+(Y+(0wuy~ zdLOFdz_%S;{FY)v-^(X^w341KAjtUPJCgzD+tS zqNn_pT}4awD_NjYk$7hS%3KipQnP?u4)8T{RfpZrHZX;CGj-2bg^+}(G*)bY-AWFZ z`m~GefsJ=@-bJ>N(IKzNNp0lVCRXascdG-#IdkvmfVpG;W@G&}*1&%JWo;#$uI;$5JpxpL55I_*+mV0Xo$M3nX;;2mPXK~wwqoY zG0nS>(n@L-qYx$(Ffs!)l6Fv*OF5tlC6y|fFwDSeP!oX@obGGG;Cirv+OjVDl zYOzA`C51oZv!Br$AA8Xh@ah zX!aqfGeWZh#R@d{02Eb?1WfS1U`Z6Bs-6PKXE_C8kkphIj+G)Q_|&ATBg2TD^Zxyy z&)2A{Q8|eYjBs5o77X|}n&&uzanL!sp^y+%3==3K4Ent|ijPBpLj%DZTSb#W{q0TvQG;9NwMWyLn6 z&do-pqWnv`jBX)FKCvF_Zd&Cd)XN4+Y_@QFmcUhoSyIMw6(a!Ax(Qxfv`PRX2KjAy zmn?4w2Hw)I}a>wAxTUHRQut%bnfVvWjrj2d!zT0i#8!g+m3Y)=`7Lcg=OeW-DtB`fDPd zpL;ij8V*yF*52sfvZ!Kd>)t)5%Jfmwk}KCts)$|muUH#!@458sj z@z$8R8$PzrJ9}nh<*^eV{JHq26^WCcKlXL^&A8fG{>uET_U>NWWyjdgb<~9Fx-XiN z9pBdsCE>ZHOYN&>l7}?Uz_xi^+^*|e15G;?AG!PbtG5@N+go;V(U-U81ReH){eyP~ zv!85hdB1n->_bogvheh{qpN0C&3eau*5SYN*@o~H*fQ;)eQ5dxXa7w3pV2?`%`xL2 zU7_}zoj?yfbvZk0>d?`9`-OL>)K02@c@1{FuWwY-^!FxDZEktAXeo6qdL QX8k{dfd>Ds+LfLE0?9y?`2YX_ diff --git a/public/images/ban_user.png b/public/images/ban_user.png deleted file mode 100644 index 1a679ed200178601f9cd85d8126c41af2e189318..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1104 zcmV-W1h4yvP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1La9XK~zXf?Nw2T z8&wp2pDT!)wa7NO?q;_E$JkA>WYe9c3zJrknXh7ZX}Y9F1oRb9ra`8W5EHb z41SG@F#6-LP@E6Zf&L5>aSBpH5jzwiL&fbBY#k8O{9W&xeZ@-uQt-;UQ=xUG){{CS6F91Vc=is3lS!db9VWRbbDVjWV z{AOAjD>6V2I;lYY%=|TSgp^q7kZ2RG`qq>$Sv62m#OQW#RG@Ws^;SilInA=}2_yU) z1(R1fP_XVsSH;~o;hB$Wz1mATf#nPbP17>%kB{1o&krZEEv5pBD}#(SrF}WMeIjgY zZ3)^H?rNiM{@!>t?61}18_(C{Kc8Fit_wmFaD$BQE{wF!v`i;*B4)ic2*A!&OdoD`Q*TCw9S{opYZd|LyLpogo>>Zl3|?xfbPt|W_kk` z{KP{C(eTl(K{}=pIuI!`B(elu5qmT{X*38a7cjJJR{DNnzt=~Ra~x+O<-bmnX?-et zW~8RBv0@|jz8mB2G3c$P^T1Rfl0*ok8iXhitSo%{-7=YC9QX*3z46L|x3f4mcyoMQ zX3K<#uZcXZxsHyZt}#LtO!WS)(5@i83y~s$kHm(q!1jqo2&>D3=;=!EL#4toD$*?$ z(Qm0VwQ@5l-fwFrVB&MT|^9ThT6fkTQcMUjos%>9f zT0)VzL9bf%x3NT5@_D{L|9Jf6zI`#H$JNID4t;CN_}0);QSv(l=!s}_J6Qi3o&bAc zdIoH6Zt9vK(Is98;=|f|L`uwwe-l1-x4W%_-A05?^8TtP+5b`&h W9b#>b+}?2j0000^f diff --git a/public/images/brush.png b/public/images/brush.png deleted file mode 100644 index c5e2ff4cc1ed989ac2562efce96ea13e106c9437..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 494 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0fR|IK~y+TosTg{ z!$1^<|FMH$6@+fatvCq#x>zV^2f>0% zaY;eJfP)<~cI)`OYj2dK`oZJ4`~UyFclZyE<4B!OXG>iFhG7^=5CmbX)tc05HCsF{ z2AiDj^?G3xMP!;LX`1#Tq{U!UeIB-LoAzXRS}vClgp^F9(WvsU=XsRR=SkOfM@Y#y zhnrh(?_beiFd#)ys8Xps2`Q1(Z3*?qv%rlc7%|-=@A@B41C@Ne1^qPap@| zaaSf$)1qNrr>>c$8yQmxCWt1Lrqqv2%zFzxDv=A%eFAF kjr!~?u%`!>{#=ZU2L{=h zOqMl?i`ht6lYyQ9+CflEO=xp^Anqvz?2h)?8FDrV>wfY|*b6 zSMq(w5QJ*A8m=l~;uZx-*L6XZ1zF}Pg7@mS4{E&a4R#q4*n_U+_!hC*fDz=$ydUGJ z(zXi~r$=jhoiNdW2{qsdQdkUJ>H;&F^`VtY5AFFA3)D(S?;VM~T;0LK6!yrx3-QDP zw*A4bF$8;ioq4z1@)SV3l|7)M2gEejbVs;*%O^L#{=BD`KuHC~rRL`RyIFDPovq-c)j&O;4Dbq-z>4hozwMsSN6F%JOKM>z%W71V7&y zl`Xbapay`!xrlKvC@(uR4Y;8%f*<3*a1jTEAAH~k)1 zSYQ3disb)Q-w7@(0lSE4s};DwA0cQ;?bHbW&01^iwg%RP)2$KIcq!aO6^c#3{ZEfuH^QR6E|2X|e?As?c&B9`FsQ=RYm%e*lzg)ZV89de8 zJbn7b?A3qP%=pibefY9FTDyGU#!n%*ynJ%(t>l--)`a1GjnLv&arDt2u0HtCo=4)L zr{0VO97-;^o z1&1E~J-O%WMq}d4`BXDG_}n{tn@daaS9TtM_3w*~zH7T0`GbF5zw*_u1BO3TOD_>5)e(zMlKL@4CO7n3jfyfrp33gxpu%;`$PDHWddHR8%jY z-H~*-?Y|fs8_)c@zlm2uLzf-d$ipvh_ox2RAyzgq0rn?Om1SjJJS{VsnS4ZMx3Km| zS!KOgv2bx)(Di@-MsHpQ<90FC|7z3r|Nke}dFq7moE-&?kF306Ll^&lzkk0g1KT?1 V-4?IsG6UVq;OXk;vd$@?2>^rbRr3G< diff --git a/public/images/button_border_focus.png b/public/images/button_border_focus.png deleted file mode 100644 index 83f333f5408079fe6056303d9277722e8e8e099a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{1|(OCFP#RYmU+53hFJ6_|M~yl{_w(X|9{)p z-#G2SnUI{GuEDn@al_^5i7AYdJN_LBZEND-X5;2%^)5PVTln~3;RH+j`oDk9KM36T z;C~;t`M>%f9}hD<$tp7RusPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0*FaOK~y+Tg^@96 z)ld|MGj?ltmx2_#`GyW}%pmv%hb*mclAy~Sqf<(Ucaeg(iw%gHLXixmWRXUwlIrk+ zBE*`~P!%sN@>1K{3w@;l=_A|kNnSv(P!Ak9_i)d5{&SA@uL0T!0P^lQu-gQ_nrCM{ z4)pZ;%S8h^V>nte@_k$U{xKW&f6mLn;Zrp@TFRC5P^bWd{>Sb5`$aq7Urh@YqR6A6 zQ>-!%RdJOg!0Aq}{(jNU`){%$PpiUQ`Bdm44sD7lSHuMHF*yA6KR|?>x@M_^G~$SS zivW@Uq>P}K0}cpK%9aiRMAe-+HY_iK2z_J(e*gK|@yx@S>uh=IT3zuzzl`9L+e51mw4v_L_ZC;@h4!lwO{gtK1n$PufM#0Q~@rrkt9is?)DmA l-&q$QYA@q8$|XrM`3Hx^O2k6sBVhml002ovPDHLkV1jA2RR910 diff --git a/public/images/games.png b/public/images/games.png deleted file mode 100644 index 11cfa770986ada45c6e87f4ef3798bdcc70f0475..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 864 zcmV-m1E2hfP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0`*BmK~zXfy;U)6 z(?AsM$VSJGY;?mzCA{H*!K0T7kDiJrFUa7JGm4RuR6h)C( zyofNw^QhbX&C8cFK!m>UQ^qN&3xdYkqdok5*Qmu=hRc^-{e6p=&R@gp*vfK->}$n|^?z3m1Rwi0SZDVerUs&0{#nUBh#5+9C6B66H6 zUDt(yqA28tEsBzq49lY+j9A>Hhg#?K_lMAW+J(7K{g zyS+q;>IOM?Euw~j$4A$)ta1lINEWBa0liR|m|P1fGar=!iU`68rz9H;S@o9m`4Qlwih?d<@mzrc@TVJiQA>p9Mla) z9H}72l87GvVK;{Dak^T<=*#)}F&~J#Qff9jozmb+mIa=pCK%TlUnA0k=y8f%KNJQ}U%h=SrRD>lMy9gWY_4PcfGNk-oPr)mIX(Ewp$0JV z{FX3z`KEny!!5!Szd44e_=K{_v_aW)?#&Ii2qxzq9qda{TyzFE8oF8ITNU?`eD$Ak q3no>85jE<8w4on1Hl}-FW#tct<_Sv9H)K8l0000c3Ejb(C%hm0^Z=TC@-A5`z&7iSUbghTdw5|w^ zqfT0K%ANvF7DwC`JXcK=JH5H|O@5-Y)&_Aou}Ak-=uGf$WVfuzZRqJ}x>sq}nwsQj@O1TaS?83{1ORa>ZrA_- diff --git a/public/images/medias.png b/public/images/medias.png deleted file mode 100644 index b5db00b0de6100acd273682fef130da57d02cfbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 853 zcmV-b1FHOqP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0_sUbK~zXf?Nm)@ z6G0T-V{aBcG=V}r=4K@z8=-Usl~G$=3yVt>Ts_nQ5f+ND;K4o4as2_eUK*j`QhSJh zH7eo?{^U?_DTVB%WRH8yt+_qt&1Ohan)HVr{opZ~eKX%T^Sw7w{$o#=rU{HOz;UmT z>pENa2i%}(8aR#%VVpocP9aKK5T`s^ZAf_FhK^ku#4SiUX^WG01yYg6b=0!N4bI$rVk-)a2$Ert7TMJ4 zvqf5iHww9Jn8$ULQX!yI9|#SgIQ#ONAQ}X;XmfMZGIb(I@znC|u>+uf43-d_*xW;Jg&2CTik$H74X6Y z+#mt!#c=ul$8FVk)ld|6Ob|<;fU2rMbZyVTby7exE8zH5@M@6|qJ9Fp6GCb3`F6h7 zHneDKYs=AzIkZhNkKHCMDwUVp`I&b*^8+E|26eDdgj+T`$@n4OjtK#25f*V+n_}Mc zJpLXWaf8xaCV;mB&!|s8bO-l%Seqib8^qvLBGBy`OzM@c4Lfk0tR)lE-!(Boi}(ZL zk5ujZ=ahjjmtk?;08}#oBFFlP z3cyHle!V~*tf@H5om*{>8@~@G(80*W^pb}D3XhA61^hTOzpk4PzeLufAL(`bQ$1H2 zT%SYX0{vWNwOG^$>r>FYf%Y8=r>2)&LaU%zRhZOFsl zc{ZIc^IY7(KuLMxL=P1sq3yNh?~6<>zxLU0^qFVFgmm-WA^$fm6Ug9FZZ&+ozQTX; zgR(n(+w3|O{2I6A{uaC!DYmDxLHogi=BX#Y$DC!c3_oa?tt*+lPb^~XiB*p8&R&?z z`s~ugcjtvJ-|hUe^REtvg+2RzSFa*nR*k96dU+zk5#mx4wuZYUWyNs+xE(F*?YMez z&V{Jp;OiQHLppx^EiQlSt|(!+Nhjdeg^h1lWJ+1>lX(#knxgRNOe=%(UDoY-%S)G< zZq{Kv%`WuoLx6w*FQfY@XbzFVV@(`8E(JC4 z=se(=#QKEGBKeqvFB@;2#)fMXAKLd$>@Tm->Iv#m>R0~sIkq9R`Fi}P>7k+T*}8qF UBt`Za0K=HU)78&qol`;+0PyUpFaQ7m diff --git a/public/images/phone.png b/public/images/phone.png deleted file mode 100644 index ce1af323749e519644d579ea9b8591a553a3e5df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 639 zcmV-_0)YLAP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0u)I^K~zXf?NmKa z+fWqEPhseQn3!5`c%ZGTBJg50QiPIph%8kW78URcgUwKp2&y^-LBxl!)Jj-@)JQDe z$e4|c9zA-@Ke&D8@{BqZ^3jYO=_-!jedoM$-@Pt8V`zBixB9n}IM@tt647eY-?6@b zaku&7>SFc#^;?xb8p__e4x(1OL!E9yakoc76jP-hQZGqlq!h(nUPm6p{KKoV+CCT{ z!{#*2C`nU_J3Xq0G3gb-gm{K%zu)H^h>av9g6M%D%QEWp5+*U+I%!3lg-kafQw03} zKCjO%UAec`INnfH|Ar}AOjPt+OaKR5B0nU<_6a$-Bg1LB$bnE>=HM`)bj~8;36Z(7 zr-Nu(GdOHndp~UR#$|u^w@Q0};dm}YP&(Wh4rSLPvYrwFUX~merQlPdBr+_~cy!a3 z=4FKP!cE>314V$vwNra>t-+yQJqoA!)3sRyE<$!v-QJ#4%aZ{_P)-CIW4M;Zt@`JpA)n$rW|+iUL5VH)bTK5d9zEadl_a&E4nNI7jT8SdROvSROJKQsUU002ovPDHLkV1hS089D#} diff --git a/public/images/settings.png b/public/images/settings.png deleted file mode 100644 index 6a23c2ba6705539e0e5e27f47795b4251a15779a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 388 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 K@$Cg{?Ee7n6cpJ2 diff --git a/public/images/status_busy.png b/public/images/status_busy.png deleted file mode 100644 index 471ff6b7f9d838ed4cf8a5b764998092e31f51d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6611 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 diff --git a/public/images/status_online.png b/public/images/status_online.png deleted file mode 100644 index dd53972613883cc7148060c0ea2565556e3d227b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5743 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 diff --git a/public/images/tbc_transfert.png b/public/images/tbc_transfert.png deleted file mode 100644 index 4ec8570333091c9e1c92a0bd5289f4ba070256a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!5f|~jv*HQcPFhCYH|>1tz=mo=xfI|(c_3D z^F$97DRZGtmq&d^xBldCdvvV#H(NO0ozNMqTVKtzOniTSdwG46{iTn)CU@QvuCd71 zmzk##l-*Qm8G1+6D>@+8+sm4xeLmN0(a>EgnOR*|kF_&Qn4KbKV8X3+tH;kuIa6AG z6U%`UCk|$Wg?+0`*DG$Ap{2ZsQ-OVlVZ&klg$kGD8PvWrJ{COAkgoTdaS`(l<$?+U zJPz-T!SeR}*`D?yZe)jRj@{ P{m9_y>gTe~DWM4fFcfn< diff --git a/public/images/webcam.svg b/public/images/webcam.svg deleted file mode 100644 index 7c3f49d..0000000 --- a/public/images/webcam.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/images/wizz.png.old b/public/images/wizz.png.old deleted file mode 100644 index d7b1d5d0765b3ad71df28650daaa4a2df48411b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 719 zcmV;=0xPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0%S=>UmrOhvemex4J8nF zvI-?!zz|b{qotsR8am#n?fU+wJ6D$6qz8}pbh`Kbr+23(=8mnciit$8*Q*SNJN5HV z>#xr~y8AH16Pm+2vuq~k+OZlw_wPQ~?IRuxTEUorN#to22U;az=10Cx`mR3zPN-^oV-){}g*r_j0tEbX=5J4 z;Kjn~{oU@ezZb&aS#JfG-!_}c*P2uWjk*EbRKy6_d59z@D-t=#&9lH3JQ-$Sc+_=# z=t@N(02+%6x*04|Y`%o%B?Qxy4Y8809v`kq@iKpOUwAbP(u3 zh)jTDE5NZ4;22-gcQkVIEO_BQej`O~$F=^~*{yvXrWKe0nKM8PQHWas6}cL@c^3P7 z8*PPJSl* zmAzJ=SfZcb*V?%288B~u;cPL1VFb4<%<#Y6rv+<2AHVg{0R>kcIo$iNX}ZaQxS zk|ReWw{*zGYkG0f^G387e=DE>$+n|M9ZyFuJbp$DLu_{+Qx0-V*A%tN{~Yj_N1M%< zt{sa|x$KS^m@5mORpc-nISelTXc+%$6=(xW63o^+2e&()e7?C=%#32QiwS_A2)ATv z1jZaUyW{_8cBN5h*H@=6L$qjgRv2Q;-Q2E)_yf#p)?`A9Rr>${002ovPDHLkV1nr^ BMos_# diff --git a/src/ui/components/chats_window/chats_window.scss b/src/ui/components/chats_window/chats_window.scss deleted file mode 100644 index 10200ef..0000000 --- a/src/ui/components/chats_window/chats_window.scss +++ /dev/null @@ -1,94 +0,0 @@ -@import "../../_base.scss" - -.chats-window { - height: 100%; - width: 100%; - - $horizontal-padding-margin: calc((2*100%)/1980); - - .tabs { - height: 2%; - width: 100%; - - display: flex; - flex-flow: row; - overflow-x: scroll; - - &::-webkit-scrollbar { - height: 0px; - } - - .tab { - height: 100%; - flex-grow: 1; - padding: 0 $horizontal-padding-margin; - - display: flex; - - button { - height: 100%; - width: 100%; - - display: flex; - flex-direction: row; - align-items: center; - - $clamped-horizontal-padding-margin: clamp(5px, $horizontal-padding-margin, $horizontal-padding-margin); - margin: 0 $clamped-horizontal-padding-margin; - padding: 0 $clamped-horizontal-padding-margin; - - white-space: nowrap; - - background-color: #EFF9F9; - border: $border-style; - - $radius: calc((6*100%)/1980); - $clamped-radius: clamp(6px, $radius, $radius); - border-radius: $clamped-radius $clamped-radius 0 0; - - font-size: $font-size; - - img { - height: $icon-size; - aspect-ratio: 1; - } - } - } - } - - .chat { - height: 98%; - width: 100%; - - background-color: #ECF6F9; - - .header { - height: 7%; - - border: $border-style; - - .info { - height: 45%; - display: flex; - flex-direction: column; - - padding-left: 2%; - - background: linear-gradient(180deg, #BFE3EB, #DEFBFE); - - font-size: $font-size; - - .room-name { - margin: 0; - margin-top: 1%; - font-weight: bold; - } - - .room-topic { - margin: 0; - color: darkgrey; - } - } - } - } -} diff --git a/src/ui/components/chats_window/conversation.rs b/src/ui/components/chats_window/conversation.rs deleted file mode 100644 index e3c5f31..0000000 --- a/src/ui/components/chats_window/conversation.rs +++ /dev/null @@ -1,87 +0,0 @@ -use dioxus::prelude::*; -use log::error; -use matrix_sdk::ruma::OwnedRoomId; - -use super::edit_section::EditSection; -use crate::base::{sync_messages, ROOMS}; -use crate::ui::components::avatar_selector::AvatarSelector; -use crate::ui::components::icons::DownArrowIcon; - -turf::style_sheet!("src/ui/components/chats_window/conversation.scss"); - -#[component] -pub(super) fn Conversation(room_id: OwnedRoomId) -> Element { - error!("Conversation {} rendering", room_id); - - let _sync_message_coro: Coroutine<()> = - use_coroutine(|_: UnboundedReceiver<_>| sync_messages(&ROOMS, room_id)); - - - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::CONVERSATION, - div { - class: ClassName::ROOM_EVENTS, - ul { - li { - class: ClassName::ROOM_EVENT, - div { - p { - class: ClassName::TITLE, - "MON POTE says:" - }, - p { - class: ClassName::CONTENT, - "Coucou mon pote", - }, - }, - }, - }, - }, - div { - class: ClassName::OTHER_AVATAR_SELECTOR_CONTAINER, - div { - class: ClassName::AVATAR_SELECTOR, - AvatarSelector {}, - }, - div { - class: ClassName::WEBCAM, - img { - src: "images/webcam.svg" - }, - }, - div { - class: ClassName::ARROW_ICON, - DownArrowIcon {} - }, - }, - div { - class: ClassName::HOLDER, - "••••••" - }, - div { - class: ClassName::EDIT_SECTION, - EditSection {}, - }, - div { - class: ClassName::MY_AVATAR_SELECTOR_CONTAINER, - div { - class: ClassName::AVATAR_SELECTOR, - AvatarSelector {}, - }, - div { - class: ClassName::WEBCAM, - img { - src: "images/webcam.svg" - }, - }, - div { - class: ClassName::ARROW_ICON, - DownArrowIcon {} - }, - }, - }, - } -} diff --git a/src/ui/components/chats_window/conversation.scss b/src/ui/components/chats_window/conversation.scss deleted file mode 100644 index ca1ae3a..0000000 --- a/src/ui/components/chats_window/conversation.scss +++ /dev/null @@ -1,113 +0,0 @@ -@import "../../_base.scss" - -.conversation { - $padding-top: 2%; - - height: calc(93% - $padding-top); - - padding-left: 2%; - padding-top: $padding-top; - - display: grid; - grid-template-columns: 75% 25%; - grid-template-rows: 70% 1% 29%; - cursor: pointer; - - .holder { - display: flex; - justify-content: center; - align-items: center; - - grid-column: 1; - grid-row: 2; - - color: darkgrey; - } - - .room-events { - display: flex; - flex-flow: column; - justify-content: flex-start; - - border: $border-style; - background-color: #FFFFFF; - - ul { - margin: 0; - padding-left: 0; - } - - li { - list-style-type: none; - } - - .room-event { - display: flex; - flex-flow: column; - justify-content: space-between; - - padding-top: 1%; - - font-size: $font-size; - - .title { - margin: 0; - } - - .content { - margin: 0; - padding-left: 2%; - } - } - } - - %selector-container { - aspect-ratio: 1; - - grid-column: 2; - - display: grid; - grid-template-columns: 10% 15% 50% 15% 10%; - grid-template-rows: 80% 20%; - - .avatar-selector { - grid-column-start: 1; - grid-column-end: 6; - aspect-ratio: 1; - } - - .webcam { - grid-column: 2; - grid-row: 2; - - aspect-ratio: 1; - } - - .arrow-icon { - grid-column: 4; - grid-row: 2; - - svg { - path:last-child { - fill: black; - } - } - } - } - - .other-avatar-selector-container { - @extend %selector-container; - - grid-row: 1; - } - - .my-avatar-selector-container { - @extend %selector-container; - - grid-row: 3; - } - - .edit-section { - grid-row: 3; - } -} diff --git a/src/ui/components/chats_window/edit_section.rs b/src/ui/components/chats_window/edit_section.rs deleted file mode 100644 index 3400783..0000000 --- a/src/ui/components/chats_window/edit_section.rs +++ /dev/null @@ -1,52 +0,0 @@ -use dioxus::prelude::*; - -turf::style_sheet!("src/ui/components/chats_window/edit_section.scss"); - -pub fn EditSection() -> Element { - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::INPUT_AREA, - - div { - class: ClassName::BUTTONS, - - button { - "😀" - }, - - button { - "😉" - }, - - button { - "😴" - }, - - button { - "🔊" - }, - }, - - textarea { - class: ClassName::EDIT, - placeholder: "Type your message here...", - }, - - div { - class: ClassName::CMD_BUTTONS, - - button { - class: ClassName::SEND_BUTTON, - - "Send" - }, - - button { - "🔎" - }, - }, - }, - } -} diff --git a/src/ui/components/chats_window/edit_section.scss b/src/ui/components/chats_window/edit_section.scss deleted file mode 100644 index 97c94b6..0000000 --- a/src/ui/components/chats_window/edit_section.scss +++ /dev/null @@ -1,55 +0,0 @@ -@import "../../_base.scss" - -.input-area { - height: 100%; - width: 100%; - - margin-bottom: 2%; - - .buttons { - $padding-top-bottom: 0.5%; - - height: calc(10% - ($padding-top-bottom * 2)); - padding-left: 2%; - padding-top: $padding-top-bottom; - padding-bottom: $padding-top-bottom; - - display: flex; - flex-direction: row; - align-items: center; - - border: $border-style; - background: linear-gradient(180deg, #F5FDFF, #E3ECF0, #F5FDFF); - - button { - @extend .aeroButton; - height: $icon-size; - - padding: 0; - margin: 0; - margin-right: 2%; - - font-size: larger; - } - } - - .edit { - height: 80%; - // Remove border from width - width: calc(100% - 2px); - - padding: 0; - margin: 0; - } - - .cmd-buttons { - height: 7%; - display: flex; - flex-direction: row; - justify-content: flex-end; - } - - .send-button { - width: 15%; - } -} diff --git a/src/ui/components/chats_window/interface.rs b/src/ui/components/chats_window/interface.rs deleted file mode 100644 index 8595910..0000000 --- a/src/ui/components/chats_window/interface.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::cell::RefCell; - -use matrix_sdk::ruma::OwnedRoomId; -use tokio::sync::broadcast::error::SendError; -use tokio::sync::broadcast::{channel, Receiver, Sender}; - -#[derive(Clone)] -pub enum Tasks { - ToggleRoom(OwnedRoomId), -} - -pub struct Interface { - sender: Sender, - receiver: RefCell>, -} - -impl Interface { - pub fn new() -> Self { - let (sender, receiver) = channel::(32); - Self { - sender, - receiver: RefCell::new(receiver), - } - } - - pub(super) fn receiver(&self) -> &RefCell> { - &self.receiver - } - - pub fn toggle_room(&self, room_id: OwnedRoomId) -> Result> { - self.sender.send(Tasks::ToggleRoom(room_id)) - } -} - -impl Default for Interface { - fn default() -> Self { - Self::new() - } -} diff --git a/src/ui/components/chats_window/mod.rs b/src/ui/components/chats_window/mod.rs deleted file mode 100644 index 3549f18..0000000 --- a/src/ui/components/chats_window/mod.rs +++ /dev/null @@ -1,171 +0,0 @@ -mod conversation; -mod edit_section; -mod navbar; - -pub(crate) mod interface; - -use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; - -use dioxus::prelude::*; -use log::{debug, error}; -use matrix_sdk::ruma::OwnedRoomId; -use tokio::sync::broadcast::Receiver; - -use crate::base::{sync_rooms, ROOMS}; -use crate::domain::model::room::Room; -use crate::infrastructure::messaging::matrix::requester::Receivers; -use conversation::Conversation; -use navbar::Navbar; - -use interface::{Interface, Tasks}; - -turf::style_sheet!("src/ui/components/chats_window/chats_window.scss"); - -#[derive(Props, Clone, PartialEq)] -pub struct ChatsWindowProps { - pub receivers: Receivers, - pub interface: Signal, -} - -fn render_rooms_tabs( - by_id_rooms: &GlobalSignal>>, - displayed_room_ids: Signal>, -) -> Vec { - let rooms_ref = by_id_rooms.read(); - let displayed_room_ids = displayed_room_ids.read(); - rooms_ref - .values() - .filter(|room| displayed_room_ids.contains(room.borrow().id())) - .map(|room| { - let room = room.borrow(); - let room_name = match room.name() { - Some(room_name) => room_name.clone(), - None => room.id().to_string(), - }; - rsx!( - div { - class: ClassName::TAB, - button { - img { - src: "/public/images/status_online.png", - }, - "{room_name}", - }, - }, - ) - }) - .collect() -} - -fn render_rooms_conversations( - by_id_rooms: &GlobalSignal>>, - displayed_room_ids: Signal>, -) -> Vec { - let rooms_ref = by_id_rooms.read(); - let displayed_room_ids = displayed_room_ids.read(); - rooms_ref - .values() - .filter(|room| displayed_room_ids.contains(room.borrow().id())) - .map(|room| { - let room = room.borrow(); - let room_id = room.id(); - rsx!(Conversation { - room_id: room_id.clone() - },) - }) - .collect() -} - -async fn handle_controls( - receiver_ref: &RefCell>, - mut displayed_room_ids: Signal>, -) { - loop { - let result = receiver_ref.borrow_mut().recv().await; - match result { - Ok(task) => match task { - Tasks::ToggleRoom(room_id) => { - error!("ON TOGGLE ROOM {}", room_id); - let mut displayed_room_ids = displayed_room_ids.write(); - match displayed_room_ids.take(&room_id) { - Some(_) => { - error!("{} room already dispayed... close it", room_id); - } - None => { - error!("{} room isn't dispayed... open it", room_id); - displayed_room_ids.insert(room_id); - } - } - } - }, - Err(err) => error!("{}", err), - } - } -} - -pub fn ChatsWindow(props: ChatsWindowProps) -> Element { - debug!("ChatsWindow rendering"); - - let receivers = &props.receivers; - let interface_ref = &props.interface; - - let displayed_room_ids = use_signal(HashSet::::new); - - let sync_rooms_coro = use_coroutine(|rx| { - to_owned![receivers]; - sync_rooms(rx, receivers, &ROOMS) - }); - sync_rooms_coro.send(true); - - let _: Coroutine<()> = use_coroutine(|_: UnboundedReceiver<_>| { - to_owned![interface_ref, displayed_room_ids]; - async move { - let interface = interface_ref.read(); - let receiver = &interface.receiver(); - handle_controls(receiver, displayed_room_ids).await - } - }); - - let rendered_rooms_tabs = render_rooms_tabs(&ROOMS, displayed_room_ids); - let rendered_rooms_conversations = render_rooms_conversations(&ROOMS, displayed_room_ids); - - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::CHATS_WINDOW, - - div { - class: ClassName::TABS, - {rendered_rooms_tabs.into_iter()}, - }, - - div { - class: ClassName::CHAT, - - div { - class: ClassName::HEADER, - - div { - class: ClassName::INFO, - - p { - class: ClassName::ROOM_NAME, - "MON POTE", - }, - - p { - class: ClassName::ROOM_TOPIC, - "LE STATUT A MON POTE", - }, - }, - - Navbar {}, - }, - - {rendered_rooms_conversations.into_iter()}, - }, - }, - } -} diff --git a/src/ui/components/chats_window/navbar.rs b/src/ui/components/chats_window/navbar.rs deleted file mode 100644 index ee463ab..0000000 --- a/src/ui/components/chats_window/navbar.rs +++ /dev/null @@ -1,50 +0,0 @@ -use dioxus::prelude::*; -use log::debug; - -turf::style_sheet!("src/ui/components/chats_window/navbar.scss"); - -pub fn Navbar() -> Element { - debug!("Navbar rendering"); - - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::NAVBAR, - - button { - style: "background: url(/public/images/add_user2.png) center no-repeat", - }, - - button { - style: "background: url(/public/images/directory.png) center no-repeat", - }, - - button { - style: "background: url(/public/images/phone.png) center no-repeat", - }, - - button { - style: "background: url(/public/images/medias.png) center no-repeat", - }, - - button { - style: "background: url(/public/images/games.png) center no-repeat", - }, - - button { - style: "background: url(/public/images/ban_user.png) center no-repeat", - }, - - button { - class: ClassName::FLEX_RIGHT_AERO_BUTTON, - style: "background: url(/public/images/brush.png) center no-repeat", - }, - - button { - class: ClassName::FLEX_LAST_BUTTON, - style: "background: url(/public/images/settings.png) center no-repeat", - }, - }, - } -} diff --git a/src/ui/components/chats_window/navbar.scss b/src/ui/components/chats_window/navbar.scss deleted file mode 100644 index 64b5596..0000000 --- a/src/ui/components/chats_window/navbar.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import "../../_base.scss" - -.navbar { - height: 55%; - padding-left: 2%; - padding-right: 2%; - - background: linear-gradient(180deg, #A9D3E0, #F0F9FA); - - display: flex; - flex-direction: row; - align-items: center; - - button { - @extend .aeroButton; - padding-right: 2%; - } - - .flex-right-aero-button { - margin-left: auto; - } - - .flex-last-button { - margin: 0; - } -} diff --git a/src/ui/components/contacts_window/contacts.rs b/src/ui/components/contacts_window/contacts.rs deleted file mode 100644 index af1efca..0000000 --- a/src/ui/components/contacts_window/contacts.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::rc::Rc; - -use dioxus::prelude::*; -use log::debug; - -use crate::ui::components::contacts_window::contacts_section::{ - filter_people_conversations, filter_room_conversations, ContactsSection, -}; - -turf::style_sheet!("src/ui/components/contacts_window/contacts.scss"); - -pub fn Contacts() -> Element { - debug!("Contacts rendering"); - - // TODO: Test overflow - // TODO: Add offline users ? - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::CONTACTS, - ContactsSection {name: "Groups", filter: Rc::new(filter_room_conversations)}, - ContactsSection {name: "Available", filter: Rc::new(filter_people_conversations)}, - }, - } -} diff --git a/src/ui/components/contacts_window/contacts.scss b/src/ui/components/contacts_window/contacts.scss deleted file mode 100644 index 25af475..0000000 --- a/src/ui/components/contacts_window/contacts.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "../../_base.scss" - -.contacts { - height: 72%; - background-color: white; -} diff --git a/src/ui/components/contacts_window/contacts_section.rs b/src/ui/components/contacts_window/contacts_section.rs deleted file mode 100644 index 83fc4e3..0000000 --- a/src/ui/components/contacts_window/contacts_section.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use dioxus::prelude::*; -use dioxus_free_icons::icons::io_icons::IoChevronDown; -use dioxus_free_icons::Icon; -use log::debug; - -use crate::base::{CHATS_WIN_INTERFACE, ROOMS}; -use crate::domain::model::room::{ByIdRooms, Room, RoomId}; -use crate::ui::components::chats_window::interface::Interface as ChatsWindowInterface; - -turf::style_sheet!("src/ui/components/contacts_window/contacts_section.scss"); - -fn ContactsArrow() -> Element { - rsx! { - style { {STYLE_SHEET} }, - Icon { - icon: IoChevronDown, - }, - } -} - -static NO_NAME_REPR: &str = "No name"; -static NO_SUBJECT_REPR: &str = "No subject"; - -pub(super) fn filter_people_conversations( - by_id_rooms: &GlobalSignal, -) -> Vec> { - let by_id_rooms = by_id_rooms.read(); - - let mut filtered_rooms = Vec::>::with_capacity(by_id_rooms.len()); - - for room in by_id_rooms.values() { - let is_direct = room.borrow().is_direct().unwrap(); - if !is_direct { - filtered_rooms.push(room.to_owned()); - } - } - filtered_rooms -} - -pub(super) fn filter_room_conversations( - by_id_rooms: &GlobalSignal, -) -> Vec> { - let by_id_rooms = by_id_rooms.read(); - - let mut filtered_rooms = Vec::>::with_capacity(by_id_rooms.len()); - - for room in by_id_rooms.values() { - let is_direct = room.borrow().is_direct().unwrap(); - if is_direct { - filtered_rooms.push(room.to_owned()); - } - } - filtered_rooms -} - -// TODO: Handle errors -fn on_clicked_room(room_id: &RoomId, chats_window_interface: &GlobalSignal) { - let _ = chats_window_interface.read().toggle_room(room_id.clone()); -} - -#[derive(Props, Clone)] -pub struct ContactsSectionProps { - name: String, - filter: Rc) -> Vec>>, -} -impl PartialEq for ContactsSectionProps { - fn eq(&self, other: &Self) -> bool { - self.name == other.name && Rc::ptr_eq(&self.filter, &other.filter) - } -} - -pub fn ContactsSection(props: ContactsSectionProps) -> Element { - debug!("ContactsSection rendering"); - - let contacts = props.filter.to_owned()(&ROOMS); - let contacts_len = contacts.len(); - - let mut show = use_signal(|| false); - - let classes = [ - ClassName::SECTION, - if *show.read() { ClassName::ACTIVE } else { "" }, - ] - .join(" "); - - let rendered_contacts = contacts.into_iter().map(|room| { - let room = room.borrow(); - - let topic = room.topic().clone().unwrap_or("".to_string()); - let name = match room.name() { - Some(name) => name.clone(), - None => NO_NAME_REPR.to_string(), - }; - let id = room.id().clone(); - let is_invited = room.is_invited().unwrap_or(false); - - let formatted = format!( - "{name} - {}", - if is_invited { - "Invited - ".to_string() - } else { - "".to_string() - } - ); - - rsx! { - li { - onclick: move |_| on_clicked_room(&id, &CHATS_WIN_INTERFACE), - img { - src: "/public/images/status_online.png", - }, - p { - {formatted}, - }, - p { - style: "color: darkgrey;", - {topic}, - }, - } - } - }); - - rsx! { - style { {STYLE_SHEET} }, - - div { - class: "{classes}", - - p { - class: ClassName::HEADER, - onclick: move |_| { - let state = *show.read(); - show.set(!state) - }, - - ContactsArrow {}, - - {format!("{} ({contacts_len})", props.name)}, - }, - - ul { - {rendered_contacts.into_iter()}, - }, - }, - } -} diff --git a/src/ui/components/contacts_window/contacts_section.scss b/src/ui/components/contacts_window/contacts_section.scss deleted file mode 100644 index 24b7531..0000000 --- a/src/ui/components/contacts_window/contacts_section.scss +++ /dev/null @@ -1,67 +0,0 @@ -@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/ui/components/contacts_window/contacts_window.scss b/src/ui/components/contacts_window/contacts_window.scss deleted file mode 100644 index 26a8865..0000000 --- a/src/ui/components/contacts_window/contacts_window.scss +++ /dev/null @@ -1,84 +0,0 @@ -@import "../../_base.scss"; - -.contactsWindow { - width: 100%; - height: 100%; - - background-color: #ECF6F9; - font-family: "Tahoma", sans-serif; - - border: thin solid #707070; - border-radius: 8px; - box-shadow: 0 0 5px #00000050; - - - .header { - height: 10%; - width: 100%; - - .titleBar { - height: 60%; - width: 100%; - background: - linear-gradient(180deg, #7DC5E3, #3883A3); - } - - .userInfo { - height: 40%; - width: 100%; - background: - linear-gradient(180deg, #00658B, #0077A6); - } - } - - .contactsNav { - height: calc(31/1080*100%); - background: - linear-gradient(180deg, #00658B, #0077A6); - - .inner { - margin-left: 1%; - margin-right: 1%; - height: 100%; - display: flex; - align-items: center; - - .flexRightAeroButton { - @extend .aeroButton; - - margin-left: auto; - } - } - } - - .search { - height: calc(38/1080*100%); - width: 100%; - - border-bottom: thin solid #e2eaf3; - - .inner { - height: 100%; - width: 98%; - padding-left: 1%; - display: flex; - flex-direction: row; - align-items: center; - - .searchInput { - height: calc(23/38*100%); - width: 100%; - margin-right: 1%; - border: thin solid #c7c7c7; - box-shadow: inset 0 0 calc(3/1080*100%) #0000002a; - font-size: 8pt; - - padding-left: 1%; - } - } - } - - .footer { - height: 10%; - } -} diff --git a/src/ui/components/contacts_window/mod.rs b/src/ui/components/contacts_window/mod.rs deleted file mode 100644 index a5abe64..0000000 --- a/src/ui/components/contacts_window/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -mod contacts; -mod contacts_section; -mod user_infos; - -use dioxus::prelude::*; -use log::debug; - -use crate::ui::components::contacts_window::contacts::Contacts; -use crate::ui::components::contacts_window::user_infos::UserInfos; - -turf::style_sheet!("src/ui/components/contacts_window/contacts_window.scss"); - -pub fn ContactsWindow() -> Element { - debug!("ContactsWindow rendering"); - - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::CONTACTS_WINDOW, - - div { - class: ClassName::HEADER, - - div { - class: ClassName::TITLE_BAR, - }, - - div { - class: ClassName::USER_INFO, - }, - - UserInfos {}, - }, - - div { - class: ClassName::CONTACTS_NAV, - div { - class: ClassName::INNER, - - button { - class: ClassName::AERO_BUTTON, - style: "background: url(/public/images/letter.png) center no-repeat", - }, - button { - class: ClassName::AERO_BUTTON, - style: "background: url(/public/images/directory.png) no-repeat center", - }, - button { - class: ClassName::AERO_BUTTON, - style: "background: url(/public/images/news.png) no-repeat center", - }, - - button { - class: ClassName::FLEX_RIGHT_AERO_BUTTON, - style: "background: url(/public/images/brush.png) no-repeat center", - }, - button { - class: ClassName::AERO_BUTTON, - style: "background: url(/public/images/settings.png) no-repeat center", - }, - - }, - - }, - - div { - class: ClassName::SEARCH, - - div { - class: ClassName::INNER, - - input { - class: ClassName::SEARCH_INPUT, - placeholder: "Find a contact...", - r#type: "text", - }, - - button { - class: ClassName::BUTTON, - style: "background: url(/public/images/add_user.png) no-repeat center", - }, - - button { - class: ClassName::BUTTON, - style: "background: url(/public/images/tbc_transfert.png) no-repeat center", - }, - }, - }, - - Contacts {}, - - div { - class: ClassName::FOOTER, - }, - }, - } -} diff --git a/src/ui/components/contacts_window/user_infos.rs b/src/ui/components/contacts_window/user_infos.rs deleted file mode 100644 index 6698c63..0000000 --- a/src/ui/components/contacts_window/user_infos.rs +++ /dev/null @@ -1,76 +0,0 @@ -use dioxus::prelude::*; -use log::debug; - -use crate::ui::components::avatar_selector::AvatarSelector; -use crate::ui::components::icons::DownArrowIcon; - -turf::style_sheet!("src/ui/components/contacts_window/user_infos.scss"); - -static MESSAGE_PLACEHOLDER: &str = ""; - -pub fn UserInfos() -> Element { - debug!("UserInfos rendering"); - - // let app_settings = use_atom_ref(cx, &APP_SETTINGS); - // let store = &app_settings.read().store; - - // println!("----------------------------------"); - // println!("UserInfos rendering"); - // // println!("store={:?}", &store); - // dbg!(&store.user_id); - // println!("----------------------------------"); - - // let user_id = store.user_id..as_ref().unwrap(); - - // let mut user_info_option = None; - let user_display_name_option: Option = None; - let user_display_name = "AIE"; - - // let user_id_option = &store.user_id; - // if user_id_option.is_some() { - // let user_id = user_id_option.as_ref().unwrap(); - // let user_info_option = store.user_infos.get(user_id); - // if user_info_option.is_some() { - // user_display_name_option = user_info_option.unwrap().display_name.as_ref(); - // } - // } - - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::USER_INFO, - - div { - class: ClassName::AVATAR_SELECTOR, - AvatarSelector {}, - }, - - div { - class: ClassName::INFO_CONTAINER, - - div { - class: ClassName::USER_ID, - p { - class: ClassName::USER_NAME, - if user_display_name_option.is_some() { "{user_display_name}" } else { "AIE" }, - }, - p { - class: ClassName::USER_STATUS, - "(Busy)", - }, - DownArrowIcon {}, - }, - - div { - class: ClassName::USER_MESSAGE, - p { - // TODO: Handle user message - {MESSAGE_PLACEHOLDER}, - } - DownArrowIcon {}, - }, - }, - }, - } -} diff --git a/src/ui/components/contacts_window/user_infos.scss b/src/ui/components/contacts_window/user_infos.scss deleted file mode 100644 index 86f7efa..0000000 --- a/src/ui/components/contacts_window/user_infos.scss +++ /dev/null @@ -1,63 +0,0 @@ -@import "../../_base.scss" - -.userInfo { - position: relative; - height: 75%; - width: 99%; - top: -75%; - left: 1%; - aspect-ratio: 1; - z-index: 1; - display: flex; - flex-direction: row; - align-items: center; - - .avatarSelector { - height: 100%; - aspect-ratio: 1; - } - - .infoContainer { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: center; - height: 100%; - width: 100%; - - .userId { - @extend .aeroButton; - - height: 30%; - width: fit-content; - display: flex; - text-align: begin; - align-items: center; - - .userName { - display: inline-block; - width: fit-content; - color: white; - margin: 0; - } - - .userStatus { - display: inline-block; - width: fit-content; - color: #B9DDE7; - } - } - - .userMessage { - @extend .aeroButton; - - width: fit-content; - height: 30%; - display: flex; - text-align: begin; - align-items: center; - margin: 0; - color: white; - } - } -} From 4ea4416165c754a87c924381b1bc4f770a2323e6 Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 8 May 2024 10:23:18 +0200 Subject: [PATCH 06/41] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20Loading=20compone?= =?UTF-8?q?nt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/loading.rs | 28 ------------ src/ui/components/loading.scss | 80 ---------------------------------- src/ui/components/mod.rs | 6 +-- 3 files changed, 2 insertions(+), 112 deletions(-) delete mode 100644 src/ui/components/loading.rs delete mode 100644 src/ui/components/loading.scss diff --git a/src/ui/components/loading.rs b/src/ui/components/loading.rs deleted file mode 100644 index e2aa62d..0000000 --- a/src/ui/components/loading.rs +++ /dev/null @@ -1,28 +0,0 @@ -use dioxus::prelude::*; -use log::debug; - -use super::spinner::Spinner; -use super::wallpaper::Wallpaper; - -turf::style_sheet!("src/ui/components/loading.scss"); - -pub fn LoadingPage() -> Element { - debug!("LoadingPage rendering"); - - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::LOADING, - - Wallpaper { - display_version: true - }, - - div { - class: ClassName::LOADING_SPINNER, - Spinner {}, - } - } - } -} diff --git a/src/ui/components/loading.scss b/src/ui/components/loading.scss deleted file mode 100644 index f26b2a1..0000000 --- a/src/ui/components/loading.scss +++ /dev/null @@ -1,80 +0,0 @@ -@import "../_base.scss" -@import "./spinner.scss" - -.loading { - height: 100%; - width: 100%; - - display: flex; - align-items: center; - justify-content: center; - - &__spinner { - height: 5%; - aspect-ratio: $logo-aspect-ratio; - - position: absolute; - - $logo-center-pos: calc(50% + ($background-height / 2) - ($logo-height / 2)); - - @media (0px < height <= calc($background-height * 5)) { - top: $logo-center-pos; - } - @media (calc($background-height * 5) < height <= calc($background-height * 6)) { - top: calc($logo-center-pos + ($background-height * 1)); - } - @media (calc($background-height * 6) < height <= calc($background-height * 8)) { - top: calc($logo-center-pos + ($background-height * 2)); - } - @media (calc($background-height * 8) < height <= calc($background-height * 10)) { - top: calc($logo-center-pos + ($background-height * 3)); - } - @media (calc($background-height * 10) < height <= calc($background-height * 12)) { - top: calc($logo-center-pos + ($background-height * 4)); - } - @media (calc($background-height * 12) < height <= calc($background-height * 14)) { - top: calc($logo-center-pos + ($background-height * 5)); - } - @media (calc($background-height * 14) < height <= calc($background-height * 16)) { - top: calc($logo-center-pos + ($background-height * 6)); - } - @media (calc($background-height * 16) < height <= calc($background-height * 18)) { - top: calc($logo-center-pos + ($background-height * 7)); - } - @media (calc($background-height * 18) < height <= calc($background-height * 20)) { - top: calc($logo-center-pos + ($background-height * 8)); - } - @media (calc($background-height * 20) < height <= calc($background-height * 22)) { - top: calc($logo-center-pos + ($background-height * 9)); - } - @media (calc($background-height * 22) < height <= calc($background-height * 24)) { - top: calc($logo-center-pos + ($background-height * 10)); - } - @media (calc($background-height * 24) < height <= calc($background-height * 26)) { - top: calc($logo-center-pos + ($background-height * 11)); - } - @media (calc($background-height * 26) < height <= calc($background-height * 28)) { - top: calc($logo-center-pos + ($background-height * 12)); - } - @media (calc($background-height * 28) < height <= calc($background-height * 30)) { - top: calc($logo-center-pos + ($background-height * 13)); - } - @media (calc($background-height * 30) < height <= calc($background-height * 32)) { - top: calc($logo-center-pos + ($background-height * 14)); - } - @media (calc($background-height * 32) < height <= calc($background-height * 34)) { - top: calc($logo-center-pos + ($background-height * 15)); - } - @media (calc($background-height * 34) < height <= calc($background-height * 36)) { - top: calc($logo-center-pos + ($background-height * 16)); - } - @media (calc($background-height * 36) < height <= calc($background-height * 38)) { - top: calc($logo-center-pos + ($background-height * 17)); - } - @media (calc($background-height * 38) < height <= calc($background-height * 40)) { - top: calc($logo-center-pos + ($background-height * 18)); - } - - background-color: get-color(greyscale, 0); - } -} diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index ecab4e3..38d0069 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -1,12 +1,10 @@ pub(crate) mod avatar_selector; pub(crate) mod button; -pub(crate) mod chats_window; -pub(crate) mod contacts_window; +pub(crate) mod chat_panel; +pub(crate) mod conversations; pub(crate) mod header; pub(crate) mod icons; -pub(crate) mod loading; pub(crate) mod login; -pub(crate) mod main_window; pub(crate) mod modal; pub(crate) mod spinner; pub(crate) mod text_input; From 32b633aad67e88ee7c60b8212902317f2367deee Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 8 May 2024 10:27:01 +0200 Subject: [PATCH 07/41] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20Header=20componen?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/images/aerobutton_border.png | Bin 1928 -> 0 bytes public/images/logo-msn.png | Bin 7385 -> 0 bytes src/ui/_base.scss | 34 ---------------------------- src/ui/components/header.rs | 25 -------------------- src/ui/components/header.scss | 13 ----------- src/ui/components/mod.rs | 1 - 6 files changed, 73 deletions(-) delete mode 100644 public/images/aerobutton_border.png delete mode 100644 public/images/logo-msn.png delete mode 100644 src/ui/components/header.rs delete mode 100644 src/ui/components/header.scss diff --git a/public/images/aerobutton_border.png b/public/images/aerobutton_border.png deleted file mode 100644 index 1f1c38834f5f5e1151835cd1529cfac2cc906b90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1928 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 DdKsOf diff --git a/src/ui/_base.scss b/src/ui/_base.scss index e535f8c..2ee904d 100644 --- a/src/ui/_base.scss +++ b/src/ui/_base.scss @@ -217,37 +217,3 @@ input { ::selection { background-color: transparent; } - - -// TODO: To remove once the design updated. -.aeroButton { - height: 50%; - min-height: 16px; - aspect-ratio: 1; - background-color: transparent; - border: 2px solid transparent; - background-size: contain !important; - margin-right: 1%; -} -.aeroButton:hover { - border-image: url(/public/images/aerobutton_border.png) 2 round; -} -.aeroButton:active { - border-image: url(/public/images/aerobutton_border_down.png) 2 round; -} - -.button { - height: 50%; - min-height: 16px; - aspect-ratio: 1; - background-color: transparent; - border: 2px solid transparent; - background-size: contain !important; - margin-right: 1%; -} -.button:hover { - border-image: url(/public/images/button_border.png) 2 round; -} -.button:active { - border-image: url(/public/images/button_border_down.png) 2 round; -} diff --git a/src/ui/components/header.rs b/src/ui/components/header.rs deleted file mode 100644 index 22294ee..0000000 --- a/src/ui/components/header.rs +++ /dev/null @@ -1,25 +0,0 @@ -use dioxus::prelude::*; - -turf::style_sheet!("src/ui/components/header.scss"); - -pub fn Header() -> Element { - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::ROOT, - img { - src: "/public/images/logo-msn.png" - } - svg { - view_box: "0 0 100 10", - text { - y: "55%", - dominant_baseline: "middle", - font_size: "5", - "Windows Live Messenger", - }, - }, - } - } -} diff --git a/src/ui/components/header.scss b/src/ui/components/header.scss deleted file mode 100644 index f087add..0000000 --- a/src/ui/components/header.scss +++ /dev/null @@ -1,13 +0,0 @@ -.root { - height: 100%; - width: 100%; - display: flex; - - img { - height: 100%; - } - - svg { - fill: white; - } -} diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index 38d0069..ac47674 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod avatar_selector; pub(crate) mod button; pub(crate) mod chat_panel; pub(crate) mod conversations; -pub(crate) mod header; pub(crate) mod icons; pub(crate) mod login; pub(crate) mod modal; From 0a0d6e745b92ded523133011ffba9dd181362ca2 Mon Sep 17 00:00:00 2001 From: Adrien Date: Thu, 9 May 2024 22:12:32 +0200 Subject: [PATCH 08/41] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20AvatarSelector=20?= =?UTF-8?q?component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/images/default-avatar.png | Bin 5791 -> 0 bytes src/ui/components/avatar_selector.rs | 54 ------------------------- src/ui/components/avatar_selector.scss | 17 -------- src/ui/components/mod.rs | 1 - 4 files changed, 72 deletions(-) delete mode 100644 public/images/default-avatar.png delete mode 100644 src/ui/components/avatar_selector.rs delete mode 100644 src/ui/components/avatar_selector.scss diff --git a/public/images/default-avatar.png b/public/images/default-avatar.png deleted file mode 100644 index 295d49d07cff35b8251ca91c3265b86213193c39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5791 zcmV;Q7GUX#P)B^$w^?cQg+5t zgVI`z)nAU(Vv^Tpn%8Na+iRcMZK2(9q}_9;-*c$nc&p%ft>JsG;eD~8U69vUhty1Oz(ZK4KvJAKPn0BIt0sQY zfV1R^x#fnp=6$l{X`tLXVyuq5=TCpnDQLKZwBt!?yC;CsP-?r7z2{7P%1ULpErQgN zzUYUx<%qcEOm@d9fYL-{vwE=Nfwbh4zv*?X;cuwll)vaMgw>S5>6XFjc(3DgtKpWw z=}L6MIBK<*!Rm3U;V*>LnZoIq!Rd;+<}ifSn#1aw!s%k1+ec=$oy6;##Os{H>Y&E! ziMi)#q~9@HqoT;{F@@Eo$?K%a?54`?q{{6!alM$r>!{1^s?6=E%Un&C{B-$RVmM}x{vlGs_C z+(?z)Qk>#VnBY{L;z^d@mcr^-p5a=c;!Bs`U7_Nk$nIUDPV5< zOqkzbqT^zu(^rsk;3?_#CpXQ<|Bspf2|=WeX$TA<{y(eZGr=CjlBajoZY zt>|*D=&sQ5w$<`=uH|*G=wXY+T8PDVvFK%!%xjy|xz_S|vgvQ5+IzFxR1Ph`Z~EyX=X(>|cbxT6?!wpW}+W>{@lNR&1z^z3gb0(2l?D zkH74Z!0nX6?w7;vnZ@p($M2-e@CmlH!~g&jHAzH4RCwBjmc-Gc`Y?t$*0dWmk|zJ2S~t*^iS7w8J;@`Vc*&YwSj z?%dh4XHTC#eFpT|r=Na8+3XqrKZyS`XaDaW+`n)0cLenLUuOKz5q|`HMgtguy`#Sk z@R$IIzdgVf;OplCKCuBh9Q>c5|5Jwo^#gkUONGwaDntU%0AkPh+ZsRuknul$i2nB- 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 Element { - rsx! { - style { {STYLE_SHEET} }, - - div { - class: ClassName::AVATAR_SELECTOR, - svg { - view_box: "0 0 100 100", - linearGradient { - id: "avatar-gradient", - x1: 1, - y1: 1, - x2: 0, - y2: 0, - stop { - offset: "0%", - stop_color: "rgb(138, 191, 209)", - } - stop { - offset: "60%", - stop_color: "rgb(236, 246, 249)", - } - }, - filter { - id: "avatar-shadow", - feDropShadow { - dx: 2, - dy: 2, - std_deviation: 3, - flood_opacity: 0.5, - }, - }, - rect { - x: "10", - y: "10", - width: "80", - height: "80", - rx: "12", - fill: "url('#avatar-gradient')", - filter: "url('#avatar-shadow')", - stroke: "grey", - }, - }, - img { - class: ClassName::AVATAR_SELECTOR_PICTURE, - src: "/public/images/default-avatar.png", - }, - }, - } -} diff --git a/src/ui/components/avatar_selector.scss b/src/ui/components/avatar_selector.scss deleted file mode 100644 index 44d843b..0000000 --- a/src/ui/components/avatar_selector.scss +++ /dev/null @@ -1,17 +0,0 @@ -.avatar-selector { - position: relative; - height: 100%; - aspect-ratio: 1; - - &__picture { - $height: 65%; - $margin: calc(100% - $height) / 2; - - position: absolute; - height: $height; - aspect-ratio: 1; - - top: $margin; - right: $margin; - } -} diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index ac47674..c7b4402 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -1,4 +1,3 @@ -pub(crate) mod avatar_selector; pub(crate) mod button; pub(crate) mod chat_panel; pub(crate) mod conversations; From 79e8dea622b3741fb67585ebe35de2d3e7514f0a Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 18:05:25 +0200 Subject: [PATCH 09/41] =?UTF-8?q?=F0=9F=9A=A7=20Add=20Account=20identity?= =?UTF-8?q?=20and=20messaging=20and=20store=20interfaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/domain/model/account.rs | 127 ++++++++++++++++++++++++ src/domain/model/common.rs | 7 ++ src/domain/model/messaging_interface.rs | 68 +++++++++++++ src/domain/model/mod.rs | 4 + src/domain/model/store_interface.rs | 23 +++++ 6 files changed, 230 insertions(+) create mode 100644 src/domain/model/account.rs create mode 100644 src/domain/model/common.rs create mode 100644 src/domain/model/messaging_interface.rs create mode 100644 src/domain/model/store_interface.rs diff --git a/Cargo.toml b/Cargo.toml index 452401f..a41d243 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ tracing = "0.1.40" tracing-web = "0.1.3" tracing-subscriber = "0.3.18" git-version = "0.3.9" +async-trait = "0.1.80" [target.'cfg(target_family = "wasm")'.dependencies] web-sys = "0.3.69" diff --git a/src/domain/model/account.rs b/src/domain/model/account.rs new file mode 100644 index 0000000..ebd4299 --- /dev/null +++ b/src/domain/model/account.rs @@ -0,0 +1,127 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use async_trait::async_trait; +use tracing::error; + +use super::{ + common::PresenceState, + messaging_interface::{ + AccountMessagingConsumerInterface, AccountMessagingProviderInterface, + RoomMessagingConsumerInterface, SpaceMessagingConsumerInterface, + }, + room::{Room, RoomId}, + space::{Space, SpaceId}, + store_interface::AccountStoreProviderInterface, +}; + +type Rooms = HashMap>; +type Spaces = HashMap>; + +pub struct Account { + display_name: RefCell>, + avatar: RefCell>>, + + #[allow(dead_code)] + presence_state: RefCell>, + + by_id_rooms: RefCell, + by_id_spaces: RefCell, + + messaging_provider: Option>, + store: &'static dyn AccountStoreProviderInterface, +} + +impl Account { + pub fn new(store: &'static dyn AccountStoreProviderInterface) -> Self { + Self { + display_name: RefCell::new(None), + avatar: RefCell::new(None), + presence_state: RefCell::new(None), + + by_id_rooms: RefCell::new(Rooms::new()), + by_id_spaces: RefCell::new(Spaces::new()), + + messaging_provider: None, + store, + } + } + + pub fn set_messaging_provider(&mut self, provider: Rc) { + self.messaging_provider = Some(provider.clone()); + } + + pub fn get_room(&self, room_id: &RoomId) -> Option> { + self.by_id_rooms.borrow().get(room_id).cloned() + } + + pub async fn get_display_name(&self) -> &RefCell> { + if self.display_name.borrow().is_none() { + if let Some(requester) = &self.messaging_provider { + let resp = requester.get_display_name().await; + if let Ok(display_name) = resp { + if let Some(display_name) = display_name { + self.display_name.borrow_mut().replace(display_name); + } else { + self.display_name.borrow_mut().take(); + } + } else { + error!("err={:?}", resp); + } + } + } + &self.display_name + } + + pub async fn get_avatar(&self) -> &RefCell>> { + if self.avatar.borrow().is_none() { + if let Some(requester) = &self.messaging_provider { + let resp = requester.get_avatar().await; + if let Ok(avatar) = resp { + if let Some(avatar) = avatar { + self.avatar.borrow_mut().replace(avatar); + } else { + self.avatar.borrow_mut().take(); + } + } else { + error!("err={:?}", resp); + } + } + } + &self.avatar + } +} + +#[async_trait(?Send)] +impl AccountMessagingConsumerInterface for Account { + async fn on_new_room(&self, room: Room) -> Rc { + let room_id = room.id().clone(); + let room = Rc::new(room); + + self.by_id_rooms + .borrow_mut() + .insert(room_id, Rc::clone(&room)); + + let room_store = Box::new(self.store.on_new_room(Rc::clone(&room))); + + room.set_store(Some(room_store)); + + room + } + + async fn on_new_space(&self, space: Space) -> Rc { + let space_id = space.id().clone(); + let space = Rc::new(space); + + self.by_id_spaces + .borrow_mut() + .insert(space_id, Rc::clone(&space)); + + let space_store = Box::new(self.store.on_new_space(Rc::clone(&space))); + + space.set_store(Some(space_store)); + + space + } +} diff --git a/src/domain/model/common.rs b/src/domain/model/common.rs new file mode 100644 index 0000000..ed71f00 --- /dev/null +++ b/src/domain/model/common.rs @@ -0,0 +1,7 @@ +use matrix_sdk::ruma::{presence::PresenceState as MatrixPresenceState, OwnedUserId}; + +pub type Avatar = Vec; + +pub type PresenceState = MatrixPresenceState; + +pub type UserId = OwnedUserId; diff --git a/src/domain/model/messaging_interface.rs b/src/domain/model/messaging_interface.rs new file mode 100644 index 0000000..9031e6d --- /dev/null +++ b/src/domain/model/messaging_interface.rs @@ -0,0 +1,68 @@ +use std::rc::Rc; + +use async_trait::async_trait; +use tokio::sync::broadcast::Receiver; + +use crate::infrastructure::messaging::matrix::worker_tasks::AccountEvent; + +use super::{ + common::{Avatar, UserId}, + room::{Room, RoomId}, + room_member::RoomMember, + space::Space, +}; + +#[async_trait(?Send)] +pub trait AccountMessagingConsumerInterface { + async fn on_new_room(&self, room: Room) -> Rc; + async fn on_new_space(&self, space: Space) -> Rc; +} + +#[async_trait(?Send)] +pub trait AccountMessagingProviderInterface { + async fn get_display_name(&self) -> anyhow::Result>; + async fn get_avatar(&self) -> anyhow::Result>>; + + async fn run_forever( + &self, + account_events_consumer: &dyn AccountMessagingConsumerInterface, + account_events_receiver: Receiver, + ) -> anyhow::Result<()>; +} + +#[async_trait(?Send)] +pub trait RoomMessagingConsumerInterface { + async fn on_invitation(&self) {} + + async fn on_new_topic(&self, _topic: Option) {} + async fn on_new_name(&self, _name: Option) {} + + #[allow(dead_code)] + async fn on_membership(&self, _member: RoomMember) {} +} + +#[async_trait(?Send)] +pub trait RoomMessagingProviderInterface { + async fn get_avatar(&self, id: &RoomId) -> anyhow::Result>; + async fn get_members(&self, id: &RoomId) -> anyhow::Result>; +} + +#[async_trait(?Send)] +pub trait SpaceMessagingConsumerInterface { + async fn on_child(&self, _room_id: RoomId) {} + async fn on_new_topic(&self, _topic: Option) {} + async fn on_new_name(&self, _name: Option) {} +} + +#[async_trait(?Send)] +pub trait SpaceMessagingProviderInterface {} + +// TODO: Rework +#[async_trait(?Send)] +pub trait MemberMessagingProviderInterface { + async fn get_avatar( + &self, + room_id: &RoomId, + user_id: &UserId, + ) -> anyhow::Result>; +} diff --git a/src/domain/model/mod.rs b/src/domain/model/mod.rs index 87868cd..9eca3df 100644 --- a/src/domain/model/mod.rs +++ b/src/domain/model/mod.rs @@ -1,2 +1,6 @@ +pub(crate) mod account; +pub(crate) mod common; +pub(crate) mod messaging_interface; pub(crate) mod room; pub(crate) mod session; +pub(crate) mod store_interface; diff --git a/src/domain/model/store_interface.rs b/src/domain/model/store_interface.rs new file mode 100644 index 0000000..7a097a9 --- /dev/null +++ b/src/domain/model/store_interface.rs @@ -0,0 +1,23 @@ +use std::rc::Rc; + +use super::room::Room; +use super::space::Space; +use crate::base::{StoreRoom, StoreSpace}; + +#[allow(dead_code)] +pub trait AccountStoreConsumerInterface {} + +pub trait AccountStoreProviderInterface { + fn on_new_room(&self, room: Rc) -> StoreRoom; + fn on_new_space(&self, space: Rc) -> StoreSpace; +} + +#[allow(dead_code)] +pub trait RoomStoreConsumerInterface {} +pub trait RoomStoreProviderInterface {} + +#[allow(dead_code)] +pub trait SpaceStoreConsumerInterface {} +pub trait SpaceStoreProviderInterface { + fn set_name(&mut self, _name: Option) {} +} From 4f9e5c538e3dbb03c5baa269ca5cba66c0ebdcdf Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 18:32:05 +0200 Subject: [PATCH 10/41] =?UTF-8?q?=F0=9F=9A=A7=20Add=20a=20first=20version?= =?UTF-8?q?=20of=20the=20mozaik=20builder=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/infrastructure/services/mod.rs | 1 + src/infrastructure/services/mozaik_builder.rs | 92 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/infrastructure/services/mozaik_builder.rs diff --git a/Cargo.toml b/Cargo.toml index a41d243..935d5b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ tracing-web = "0.1.3" tracing-subscriber = "0.3.18" git-version = "0.3.9" async-trait = "0.1.80" +image = "0.25.1" [target.'cfg(target_family = "wasm")'.dependencies] web-sys = "0.3.69" diff --git a/src/infrastructure/services/mod.rs b/src/infrastructure/services/mod.rs index 6e340f5..4244fb5 100644 --- a/src/infrastructure/services/mod.rs +++ b/src/infrastructure/services/mod.rs @@ -1 +1,2 @@ +pub(crate) mod mozaik_builder; pub(crate) mod random_svg_generators; diff --git a/src/infrastructure/services/mozaik_builder.rs b/src/infrastructure/services/mozaik_builder.rs new file mode 100644 index 0000000..d9d5375 --- /dev/null +++ b/src/infrastructure/services/mozaik_builder.rs @@ -0,0 +1,92 @@ +use std::io::Cursor; + +use image::imageops::FilterType; +use image::io::Reader; +use image::{DynamicImage, ImageFormat}; +use image::{GenericImage, RgbImage}; +use tracing::{error, warn}; + +fn from_raw_to_image(raw: &Vec) -> Option { + match Reader::new(Cursor::new(raw)).with_guessed_format() { + Ok(reader) => match reader.decode() { + Ok(image) => return Some(image), + Err(err) => error!("Unable to decode the image: {}", err), + }, + Err(err) => { + error!("Unable to read the image: {}", err) + } + } + None +} + +pub fn create_mozaik( + width_px: u32, + height_px: u32, + images: &[Vec], + padding_image: &Option>, +) -> Vec { + let placeholder = DynamicImage::new_rgb8(128, 128); + + let images: Vec> = images.iter().map(from_raw_to_image).collect(); + let padding_image = if let Some(padding_image) = padding_image { + from_raw_to_image(padding_image) + } else { + None + }; + + let mut bytes: Vec = Vec::new(); + + let mut allocations: Vec<&Option> = vec![]; + let mut images_per_row = 1; + let mut images_per_col = 1; + + match images.len() { + 0 => { + allocations.push(&padding_image); + } + 1 => { + allocations.push(&images[0]); + } + 2 => { + allocations.extend_from_slice(&[&images[0], &images[1], &images[1], &images[0]]); + images_per_row = 2; + images_per_col = 2; + } + _ => { + // TODO: Manage other cases + warn!("For now, we only manage the rendering of mozaic with less than 3 images"); + return bytes; + } + } + + let image_width_px = width_px / images_per_row; + let image_height_px = height_px / images_per_col; + + let mut output = RgbImage::new(width_px, height_px); + + let mut row_pos = 0; + for (index, image) in allocations.iter().enumerate() { + if index > 0 && index % images_per_row as usize == 0 { + row_pos += 1; + } + + let col_pos = index - (images_per_row as usize * row_pos); + + let image = *image; + + let scaled = image + .as_ref() + .unwrap_or(&placeholder) + .resize_to_fill(image_width_px, image_height_px, FilterType::Nearest) + .into_rgb8(); + + let output_image_pos_x = col_pos as u32 * image_width_px; + let output_image_pos_y = row_pos as u32 * image_height_px; + + let _ = output.copy_from(&scaled, output_image_pos_x, output_image_pos_y); + } + + let _ = output.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Jpeg); + + bytes +} From 0190cf91659d346beeb843616fa39e37829635ae Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 19:13:46 +0200 Subject: [PATCH 11/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rework=20the=20Room?= =?UTF-8?q?=20entity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/room.rs | 250 ++++++++++++++++++++++++--------------- 1 file changed, 157 insertions(+), 93 deletions(-) diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index 131deeb..a54cf01 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -1,24 +1,52 @@ use std::cell::RefCell; -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; +use std::rc::Rc; +use async_trait::async_trait; +use futures::future::{join, join_all}; use matrix_sdk::ruma::OwnedRoomId; -use matrix_sdk::{Room as MatrixRoom, RoomState as MatrixRoomState}; -use tracing::error; +use matrix_sdk::RoomState as MatrixRoomState; +use tracing::{debug, error, trace}; -pub(crate) type RoomId = OwnedRoomId; +use super::{ + common::{Avatar, UserId}, + messaging_interface::{RoomMessagingConsumerInterface, RoomMessagingProviderInterface}, + room_member::RoomMember, + space::SpaceId, + store_interface::RoomStoreProviderInterface, +}; -#[derive(Clone, Debug)] -pub(crate) struct Room { +use crate::infrastructure::services::mozaik_builder::create_mozaik; + +pub type RoomId = OwnedRoomId; + +pub struct Room { id: RoomId, - name: Option, + + #[allow(dead_code)] + spaces: Vec, + + name: RefCell>, topic: Option, is_direct: Option, state: Option, + avatar: RefCell>, + members: RefCell>, + + messaging_provider: Option>, + store: RefCell>>, +} + +impl PartialEq for Room { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } } impl Room { - fn new( + pub fn new( id: RoomId, + spaces: Vec, name: Option, topic: Option, is_direct: Option, @@ -26,120 +54,156 @@ impl Room { ) -> Self { Self { id, - name, + + spaces, + name: RefCell::new(name), topic, is_direct, state, + avatar: RefCell::new(None), + members: RefCell::new(HashMap::new()), + + messaging_provider: None, + store: RefCell::new(None), } } - // TODO: Use a factory instead... - pub async fn from_matrix_room(matrix_room: &MatrixRoom) -> Self { - // let room_topic = matrix_room.topic().map(RefCell::new); - - let id = RoomId::from(matrix_room.room_id()); - let name = matrix_room.name(); - let room_topic = matrix_room.topic(); - let is_direct = match matrix_room.is_direct().await { - Ok(is_direct) => Some(is_direct), - Err(err) => { - error!("Unable to know if the room \"{id}\" is direct: {err}"); - None - } - }; - let state = Some(matrix_room.state()); - - Self::new(id, name, room_topic, is_direct, state) - - // room.timeline.subscribe().await - - // Arc::new(matrix_room.to_owned()), + pub fn set_messaging_provider( + &mut self, + messaging_provider: Rc, + ) { + self.messaging_provider = Some(messaging_provider); } - pub fn id(&self) -> &OwnedRoomId { + pub fn set_store(&self, store: Option>) { + *self.store.borrow_mut() = store; + } + + pub fn id(&self) -> &RoomId { &self.id } - pub fn name(&self) -> &Option { - &self.name + pub fn name(&self) -> Option { + self.name.borrow().clone() } + #[allow(dead_code)] pub fn topic(&self) -> &Option { &self.topic } + + #[allow(dead_code)] pub fn set_topic(&mut self, topic: Option) { self.topic = topic; } + #[allow(dead_code)] pub fn is_direct(&self) -> &Option { &self.is_direct } + #[allow(dead_code)] pub fn state(&self) -> &Option { &self.state } + + #[allow(dead_code)] pub fn is_invited(&self) -> Option { - match self.state { - Some(state) => Some(state == MatrixRoomState::Invited), - None => None, + self.state.map(|state| state == MatrixRoomState::Invited) + } + + #[allow(dead_code)] + fn add_member(&self, member: RoomMember) { + self.members.borrow_mut().insert(member.id.clone(), member); + } + + pub async fn get_avatar(&self) -> Option { + if self.avatar.borrow().is_none() { + if let Some(requester) = &self.messaging_provider { + let resp = requester.get_avatar(&self.id).await; + if let Ok(avatar) = resp { + if let Some(avatar) = avatar { + return Some(avatar); + } else { + debug!("The room has no avatar... let's generate one"); + match self.render_room_avatar_with_members().await { + Ok(avatar) => { + if let Some(avatar) = avatar { + return Some(avatar); + } + } + Err(err) => { + error!("err={}", err); + } + } + } + } else { + error!("err={:?}", resp); + } + } } + self.avatar.borrow().clone() + } + + async fn render_room_avatar_with_members(&self) -> anyhow::Result> { + if let Some(requester) = &self.messaging_provider { + match requester.get_members(&self.id).await { + Ok(members) => { + let mut account_member = None::<&RoomMember>; + let mut other_members = Vec::<&RoomMember>::new(); + + for member in &members { + if member.is_account_user { + account_member = Some(member); + } else { + other_members.push(member); + } + } + + let other_avatars_futures = + join_all(other_members.iter().map(|member| member.get_avatar())); + + let (other_avatars, account_avatar) = if let Some(account_member) = + account_member + { + join(other_avatars_futures, account_member.get_avatar()).await + } else { + ( + join_all(other_members.iter().map(|member| member.get_avatar())).await, + None, + ) + }; + + let other_avatars: Vec> = other_avatars.into_iter().flatten().collect(); + + return Ok(Some(create_mozaik( + 256, + 256, + &other_avatars, + &account_avatar, + ))); + } + Err(err) => { + error!("err={}", err); + } + } + } + Ok(None) } } -pub type ByIdRooms = HashMap>; - -// pub type ByIdRooms = HashMap>; - -// #[derive(Clone)] -// pub struct Room { -// // pub matrix_room: Arc, -// pub topic: Option>, -// pub members: HashMap, -// pub is_direct: Option, -// // pub timeline: Arc, -// } - -// impl Room { -// pub async fn new( -// matrix_room: Arc, -// topic: Option>, -// is_direct: Option, -// ) -> Self { -// // TODO: Filter events -// // let timeline = Arc::new(matrix_room.timeline_builder().build().await.ok().unwrap()); -// Self { -// matrix_room, -// topic, -// members: HashMap::new(), -// is_direct, -// // timeline, -// } -// } - -// pub async fn from_matrix_room(matrix_room: &MatrixRoom) -> Self { -// let room_topic = matrix_room.topic().map(RefCell::new); - -// Self::new( -// Arc::new(matrix_room.to_owned()), -// room_topic, -// matrix_room.is_direct().await.ok(), -// ) -// .await -// // room.timeline.subscribe().await -// } - -// pub fn name(&self) -> Option { -// self.matrix_room.name() -// } - -// pub fn id(&self) -> OwnedRoomId { -// OwnedRoomId::from(self.matrix_room.room_id()) -// } -// } - -// 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() -// } -// } +#[async_trait(?Send)] +impl RoomMessagingConsumerInterface for Room { + async fn on_invitation(&self) { + trace!("Room::on_invitation"); + } + async fn on_membership(&self, member: RoomMember) { + trace!("Room::on_membership({:?})", member); + } + async fn on_new_topic(&self, topic: Option) { + trace!("Room::on_new_topic({:?})", topic); + } + async fn on_new_name(&self, name: Option) { + trace!("Room::on_new_name({:?})", name); + } +} From bfa1539d23287d31d20f1bff7b499c6438264516 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 19:24:32 +0200 Subject: [PATCH 12/41] =?UTF-8?q?=F0=9F=9A=A7=20Add=20Space=20identity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/mod.rs | 1 + src/domain/model/space.rs | 91 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/domain/model/space.rs diff --git a/src/domain/model/mod.rs b/src/domain/model/mod.rs index 9eca3df..93fb28e 100644 --- a/src/domain/model/mod.rs +++ b/src/domain/model/mod.rs @@ -3,4 +3,5 @@ pub(crate) mod common; pub(crate) mod messaging_interface; pub(crate) mod room; pub(crate) mod session; +pub(crate) mod space; pub(crate) mod store_interface; diff --git a/src/domain/model/space.rs b/src/domain/model/space.rs new file mode 100644 index 0000000..034fac5 --- /dev/null +++ b/src/domain/model/space.rs @@ -0,0 +1,91 @@ +use std::{cell::RefCell, collections::HashSet, rc::Rc}; + +use async_trait::async_trait; +use matrix_sdk::ruma::OwnedRoomId; +use tracing::error; + +use super::{ + common::Avatar, + messaging_interface::{SpaceMessagingConsumerInterface, SpaceMessagingProviderInterface}, + room::RoomId, + store_interface::SpaceStoreProviderInterface, +}; + +pub type SpaceId = OwnedRoomId; + +// TODO: Add membership? +pub struct Space { + id: SpaceId, + + name: RefCell>, + topic: RefCell>, + + #[allow(dead_code)] + avatar: RefCell>, + + children: RefCell>, // We don´t expect to manage nested spaces + + messaging_provider: Option>, + store: RefCell>>, +} + +impl PartialEq for Space { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Space { + pub fn new(id: SpaceId, name: Option, topic: Option) -> Self { + Self { + id, + + name: RefCell::new(name), + topic: RefCell::new(topic), + + #[allow(dead_code)] + avatar: RefCell::new(None), + + children: RefCell::new(HashSet::new()), + + messaging_provider: None, + store: RefCell::new(None), + } + } + + pub fn set_messaging_provider(&mut self, provider: Rc) { + self.messaging_provider = Some(provider); + } + + pub fn set_store(&self, store: Option>) { + *self.store.borrow_mut() = store; + } + + pub fn id(&self) -> &SpaceId { + &self.id + } + + pub fn name(&self) -> Option { + self.name.borrow().clone() + } +} + +#[async_trait(?Send)] +impl SpaceMessagingConsumerInterface for Space { + async fn on_child(&self, room_id: RoomId) { + error!("Space::on_child({room_id})"); + self.children.borrow_mut().insert(room_id); + } + async fn on_new_topic(&self, topic: Option) { + error!("Space::on_new_topic({:?})", topic); + *self.topic.borrow_mut() = topic; + } + async fn on_new_name(&self, name: Option) { + error!("Space::on_new_name({:?})", name); + self.name.borrow_mut().clone_from(&name); + + if let Some(store) = self.store.borrow_mut().as_mut() { + store.set_name(name); + } + } +} From c2918fbc78d578514ab86a35773b519106982e39 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 19:56:39 +0200 Subject: [PATCH 13/41] =?UTF-8?q?=F0=9F=9A=A7=20Add=20RoomMember=20value?= =?UTF-8?q?=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/mod.rs | 1 + src/domain/model/room.rs | 6 ++- src/domain/model/room_member.rs | 90 +++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/domain/model/room_member.rs diff --git a/src/domain/model/mod.rs b/src/domain/model/mod.rs index 93fb28e..8a6ead6 100644 --- a/src/domain/model/mod.rs +++ b/src/domain/model/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod account; pub(crate) mod common; pub(crate) mod messaging_interface; pub(crate) mod room; +pub(crate) mod room_member; pub(crate) mod session; pub(crate) mod space; pub(crate) mod store_interface; diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index a54cf01..d7c6de9 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -114,7 +114,9 @@ impl Room { #[allow(dead_code)] fn add_member(&self, member: RoomMember) { - self.members.borrow_mut().insert(member.id.clone(), member); + self.members + .borrow_mut() + .insert(member.id().clone(), member); } pub async fn get_avatar(&self) -> Option { @@ -153,7 +155,7 @@ impl Room { let mut other_members = Vec::<&RoomMember>::new(); for member in &members { - if member.is_account_user { + if member.is_account_user() { account_member = Some(member); } else { other_members.push(member); diff --git a/src/domain/model/room_member.rs b/src/domain/model/room_member.rs new file mode 100644 index 0000000..8714c74 --- /dev/null +++ b/src/domain/model/room_member.rs @@ -0,0 +1,90 @@ +use std::{ + cell::RefCell, + fmt::{Debug, Formatter}, + rc::Rc, +}; + +use matrix_sdk::{room::RoomMember as MatrixRoomMember, ruma::OwnedRoomId}; +use tracing::error; + +use super::{ + common::{Avatar, UserId}, + messaging_interface::MemberMessagingProviderInterface, + room::RoomId, +}; + +#[derive(Clone)] +pub struct RoomMember { + id: UserId, + room_id: RoomId, + is_account_user: bool, + + #[allow(dead_code)] + avatar: RefCell>, + + messaging_provider: Rc, +} + +impl RoomMember { + fn new( + id: UserId, + room_id: RoomId, + is_account_user: bool, + messaging_provider: Rc, + ) -> Self { + Self { + id, + room_id, + is_account_user, + avatar: RefCell::new(None), + messaging_provider, + } + } + + // TODO: Use a factory instead... + pub async fn from_matrix( + matrix_room_member: &MatrixRoomMember, + room_id: &OwnedRoomId, + messaging_provider: Rc, + ) -> Self { + Self::new( + UserId::from(matrix_room_member.user_id()), + room_id.clone(), + matrix_room_member.is_account_user(), + messaging_provider, + ) + } + + pub fn id(&self) -> &UserId { + &self.id + } + + #[allow(dead_code)] + pub fn room_id(&self) -> &RoomId { + &self.room_id + } + + pub fn is_account_user(&self) -> bool { + self.is_account_user + } + + pub async fn get_avatar(&self) -> Option { + match self + .messaging_provider + .get_avatar(&self.room_id, &self.id) + .await + { + Ok(avatar) => avatar, + Err(err) => { + error!("err={}", err); + None + } + } + } +} + +impl Debug for RoomMember { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_struct("RoomMember").field("id", &self.id).finish() + } +} From 692a71faef94770d7f160fab5f88e261e7887e51 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 20:09:19 +0200 Subject: [PATCH 14/41] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20clippy=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/random_svg_generators.rs | 4 ++-- src/ui/components/icons.rs | 4 ---- src/ui/components/login.rs | 19 ++++++++++--------- src/ui/components/modal.rs | 17 +++++++++-------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/infrastructure/services/random_svg_generators.rs b/src/infrastructure/services/random_svg_generators.rs index ed1a7c8..0132010 100644 --- a/src/infrastructure/services/random_svg_generators.rs +++ b/src/infrastructure/services/random_svg_generators.rs @@ -116,7 +116,7 @@ async fn fetch_text(req: String) -> RequestResult { async fn fetch_dicebear_svg( r#type: &DicebearType, - req_fields: &Vec, + req_fields: &[String], placeholder_fetcher: Option>>>, ) -> String { // TODO: Use configuration file @@ -146,7 +146,7 @@ async fn fetch_dicebear_svg( } #[cfg(feature = "desktop")] -fn gen_placeholder_fetcher<'a>(path: &'static str) -> Box>> { +fn gen_placeholder_fetcher(path: &'static str) -> Box>> { let path = format!("./public/{}", &path); Box::new(async move { match read_to_string(&path).await { diff --git a/src/ui/components/icons.rs b/src/ui/components/icons.rs index e978c23..99fcb3d 100644 --- a/src/ui/components/icons.rs +++ b/src/ui/components/icons.rs @@ -3,7 +3,6 @@ use dioxus::prelude::*; use dioxus_free_icons::icons::fa_solid_icons::{ FaComments, FaLayerGroup, FaMagnifyingGlass, FaPeopleGroup, }; -use dioxus_free_icons::icons::md_navigation_icons::MdArrowDropDown; use dioxus_free_icons::{Icon, IconShape}; turf::style_sheet!("src/ui/components/icons.scss"); @@ -26,9 +25,6 @@ macro_rules! transparent_icon { }; } -// TODO: Remove this icon once the conversation panel finished -transparent_icon!(DownArrowIcon, MdArrowDropDown); - transparent_icon!(SearchIcon, FaMagnifyingGlass); transparent_icon!(SpacesIcon, FaLayerGroup); transparent_icon!(ChatsIcon, FaComments); diff --git a/src/ui/components/login.rs b/src/ui/components/login.rs index 87f981d..2f68653 100644 --- a/src/ui/components/login.rs +++ b/src/ui/components/login.rs @@ -538,7 +538,7 @@ fn generate_modal( on_confirm: on_confirm, div { - {rendered_suggestions.into_iter()} + {rendered_suggestions.iter()} } } } @@ -608,15 +608,16 @@ pub fn Login() -> Element { generate_random_svg_shape(Some(&shape_config)).await }); - let avatar = match &*random_avatar_future.read_unchecked() { - Some(svg) => Some(rsx! { - div { - class: ClassName::LOGIN_AVATAR_CONTENT, - dangerous_inner_html: svg.as_str(), + let avatar = (*random_avatar_future.read_unchecked()) + .as_ref() + .map(|svg| { + rsx! { + div { + class: ClassName::LOGIN_AVATAR_CONTENT, + dangerous_inner_html: svg.as_str(), + } } - }), - None => None, - }; + }); if *spinner_animated.read() && SESSION.read().is_logged { debug!("Stop spinner"); diff --git a/src/ui/components/modal.rs b/src/ui/components/modal.rs index 59a5016..483c8ea 100644 --- a/src/ui/components/modal.rs +++ b/src/ui/components/modal.rs @@ -59,15 +59,16 @@ pub fn Modal(props: ModalProps) -> Element { let random_figure_future = use_resource(move || async move { generate_random_svg_avatar(avatar_config).await }); - let icon = match &*random_figure_future.read_unchecked() { - Some(svg) => Some(rsx! { - div { - class: ClassName::MODAL_CONTENT_ICON_PLACEHOLDER, - dangerous_inner_html: svg.as_str(), + let icon = (*random_figure_future.read_unchecked()) + .as_ref() + .map(|svg| { + rsx! { + div { + class: ClassName::MODAL_CONTENT_ICON_PLACEHOLDER, + dangerous_inner_html: svg.as_str(), + } } - }), - None => None, - }; + }); let button_class = match &props.severity { Severity::Ok => SuccessButton, From e3a6ec985859b7c6f99667c8adafe26284158de2 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 22:16:49 +0200 Subject: [PATCH 15/41] =?UTF-8?q?=E2=9C=A8=20Add=20new=20messaging=20Worke?= =?UTF-8?q?rTask?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/domain/model/messaging_interface.rs | 3 +- .../messaging/matrix/worker_tasks.rs | 50 +++++++++++++++++-- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 935d5b3..a00b679 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ tracing-web = "0.1.3" tracing-subscriber = "0.3.18" git-version = "0.3.9" async-trait = "0.1.80" +tokio-stream = "0.1.15" image = "0.25.1" [target.'cfg(target_family = "wasm")'.dependencies] diff --git a/src/domain/model/messaging_interface.rs b/src/domain/model/messaging_interface.rs index 9031e6d..fd8183b 100644 --- a/src/domain/model/messaging_interface.rs +++ b/src/domain/model/messaging_interface.rs @@ -3,14 +3,13 @@ use std::rc::Rc; use async_trait::async_trait; use tokio::sync::broadcast::Receiver; -use crate::infrastructure::messaging::matrix::worker_tasks::AccountEvent; - use super::{ common::{Avatar, UserId}, room::{Room, RoomId}, room_member::RoomMember, space::Space, }; +use crate::infrastructure::messaging::matrix::account_event::AccountEvent; #[async_trait(?Send)] pub trait AccountMessagingConsumerInterface { diff --git a/src/infrastructure/messaging/matrix/worker_tasks.rs b/src/infrastructure/messaging/matrix/worker_tasks.rs index e81d4e6..0c0494a 100644 --- a/src/infrastructure/messaging/matrix/worker_tasks.rs +++ b/src/infrastructure/messaging/matrix/worker_tasks.rs @@ -1,19 +1,32 @@ use std::fmt::{Debug, Formatter}; +use matrix_sdk::{ + room::RoomMember, + ruma::{OwnedRoomId, OwnedUserId}, +}; + use crate::utils::Sender; #[derive(Debug)] pub enum LoginStyle { - // SessionRestore(Session), Password(String, String), } pub enum WorkerTask { - // Init(AsyncProgramStore, ClientReply<()>), - // Init(ClientReply<()>), - Init(Sender<()>), - //Login(LoginStyle, ClientReply), + Init(Sender>), Login(LoginStyle, Sender>), + RunForever(Sender<()>), + GetDisplayName(Sender>>), + GetAvatar(Sender>>>), + + GetRoomAvatar(OwnedRoomId, Sender>>>), + GetRoomMembers(OwnedRoomId, Sender>>), + + GetRoomMemberAvatar( + OwnedRoomId, + OwnedUserId, + Sender>>>, + ), } impl Debug for WorkerTask { @@ -24,11 +37,38 @@ impl Debug for WorkerTask { .field(&format_args!("_")) // .field(&format_args!("_")) .finish(), + WorkerTask::RunForever(_) => f + .debug_tuple("WorkerTask::RunForever") + .field(&format_args!("_")) + .finish(), WorkerTask::Login(style, _) => f .debug_tuple("WorkerTask::Login") .field(style) // .field(&format_args!("_")) .finish(), + WorkerTask::GetDisplayName(_) => f + .debug_tuple("WorkerTask::GetDisplayName") + .field(&format_args!("_")) + .finish(), + WorkerTask::GetAvatar(_) => f + .debug_tuple("WorkerTask::GetAvatar") + .field(&format_args!("_")) + .finish(), + + WorkerTask::GetRoomAvatar(id, _) => f + .debug_tuple("WorkerTask::GetRoomAvatar") + .field(id) + .finish(), + WorkerTask::GetRoomMembers(id, _) => f + .debug_tuple("WorkerTask::GetRoomMembers") + .field(id) + .finish(), + + WorkerTask::GetRoomMemberAvatar(room_id, user_id, _) => f + .debug_tuple("WorkerTask::GetRoomMemberAvatar") + .field(room_id) + .field(user_id) + .finish(), } } } From ef41c0bd485cda4ffd82d5119c91f10c9efd86fa Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 22:18:45 +0200 Subject: [PATCH 16/41] =?UTF-8?q?=E2=9C=A8=20Add=20events=20shared=20by=20?= =?UTF-8?q?Matrix=20client=20and=20Requester?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../messaging/matrix/account_event.rs | 51 ++++++++++++++++ src/infrastructure/messaging/matrix/mod.rs | 2 + .../messaging/matrix/room_event.rs | 58 +++++++++++++++++++ src/utils.rs | 1 + 4 files changed, 112 insertions(+) create mode 100644 src/infrastructure/messaging/matrix/account_event.rs create mode 100644 src/infrastructure/messaging/matrix/room_event.rs diff --git a/src/infrastructure/messaging/matrix/account_event.rs b/src/infrastructure/messaging/matrix/account_event.rs new file mode 100644 index 0000000..fecddf4 --- /dev/null +++ b/src/infrastructure/messaging/matrix/account_event.rs @@ -0,0 +1,51 @@ +use std::fmt::{Debug, Formatter}; + +use matrix_sdk::{ruma::OwnedRoomId, RoomState}; + +use super::room_event::RoomEventsReceiver; +use crate::{domain::model::space::SpaceId, utils::Sender}; + +#[derive(Clone)] +pub enum AccountEvent { + NewRoom( + OwnedRoomId, + Vec, + Option, + Option, + Option, + RoomState, + RoomEventsReceiver, + Sender, + ), + + NewSpace( + OwnedRoomId, + Option, + Option, + RoomEventsReceiver, + Sender, + ), +} + +impl Debug for AccountEvent { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::NewRoom(id, spaces, name, topic, is_direct, state, _events_receiver, _sender) => { + f.debug_tuple("AccountEvent::NewRoom") + .field(id) + .field(spaces) + .field(name) + .field(topic) + .field(is_direct) + .field(state) + .finish() + } + Self::NewSpace(id, name, topic, _events_receiver, _sender) => f + .debug_tuple("AccountEvent::NewSpace") + .field(id) + .field(name) + .field(topic) + .finish(), + } + } +} diff --git a/src/infrastructure/messaging/matrix/mod.rs b/src/infrastructure/messaging/matrix/mod.rs index e0e9b7d..0856f53 100644 --- a/src/infrastructure/messaging/matrix/mod.rs +++ b/src/infrastructure/messaging/matrix/mod.rs @@ -1,3 +1,5 @@ +pub(crate) mod account_event; pub(crate) mod client; pub(crate) mod requester; +pub(crate) mod room_event; pub(crate) mod worker_tasks; diff --git a/src/infrastructure/messaging/matrix/room_event.rs b/src/infrastructure/messaging/matrix/room_event.rs new file mode 100644 index 0000000..ca6690e --- /dev/null +++ b/src/infrastructure/messaging/matrix/room_event.rs @@ -0,0 +1,58 @@ +use std::fmt::{Debug, Formatter}; + +use matrix_sdk::ruma::{OwnedRoomId, OwnedUserId}; +use tokio::sync::broadcast::Receiver; + +#[derive(Clone)] +pub enum RoomEvent { + Invitation(), + + #[allow(dead_code)] + Membership(OwnedUserId, bool), + + NewTopic(Option), + NewName(Option), + NewChild(OwnedRoomId), +} + +impl Debug for RoomEvent { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Self::Invitation() => f + .debug_tuple("RoomEvent::Invitation") + .field(&format_args!("_")) + .finish(), + Self::Membership(user_id, is_account_user) => f + .debug_tuple("RoomEvent::Membership") + .field(user_id) + .field(is_account_user) + .finish(), + Self::NewTopic(topic) => f.debug_tuple("RoomEvent::NewTopic").field(topic).finish(), + Self::NewName(name) => f.debug_tuple("RoomEvent::NewName").field(name).finish(), + Self::NewChild(room_id) => f + .debug_tuple("SpaceEvent::NewChild") + .field(room_id) + .finish(), + } + } +} + +pub struct RoomEventsReceiver(Receiver); + +impl Clone for RoomEventsReceiver { + fn clone(&self) -> Self { + Self(self.0.resubscribe()) + } +} + +impl RoomEventsReceiver { + pub fn new(inner: Receiver) -> Self { + Self(inner) + } +} + +impl From for Receiver { + fn from(val: RoomEventsReceiver) -> Self { + val.0 + } +} diff --git a/src/utils.rs b/src/utils.rs index 64baf06..c612e1f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,6 +8,7 @@ impl Receiver { } } +#[derive(Clone)] pub struct Sender(_Sender); // TODO: Handle error From 0a936dd12b0c9db8954c0d1bab531a7455d9e07a Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 22:20:32 +0200 Subject: [PATCH 17/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Rework=20the=20Ma?= =?UTF-8?q?trix=20messaging=20Requester?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../messaging/matrix/requester.rs | 273 +++++++++++++++--- 1 file changed, 236 insertions(+), 37 deletions(-) diff --git a/src/infrastructure/messaging/matrix/requester.rs b/src/infrastructure/messaging/matrix/requester.rs index c984ad3..6e934de 100644 --- a/src/infrastructure/messaging/matrix/requester.rs +++ b/src/infrastructure/messaging/matrix/requester.rs @@ -1,64 +1,263 @@ -use std::cell::RefCell; -use std::sync::Arc; +use std::{collections::HashMap, rc::Rc}; -use matrix_sdk::Client as MatrixClient; -use tokio::sync::broadcast::Receiver; -use tokio::sync::mpsc::UnboundedSender; +use async_trait::async_trait; +use futures::future::join_all; +use tokio::{ + select, + sync::{broadcast::Receiver, mpsc::UnboundedSender}, +}; +use tokio_stream::{wrappers::BroadcastStream, StreamExt, StreamMap}; +use tracing::error; -use super::client::RoomEvent; -use super::worker_tasks::{LoginStyle, WorkerTask}; -use crate::utils::oneshot; +use super::{ + account_event::AccountEvent, + room_event::RoomEvent, + worker_tasks::{LoginStyle, WorkerTask}, +}; +use crate::{ + domain::model::{ + common::{Avatar, UserId}, + messaging_interface::{ + AccountMessagingConsumerInterface, AccountMessagingProviderInterface, + MemberMessagingProviderInterface, RoomMessagingConsumerInterface, + RoomMessagingProviderInterface, SpaceMessagingConsumerInterface, + SpaceMessagingProviderInterface, + }, + room::{Room, RoomId}, + room_member::RoomMember, + space::Space, + }, + utils::oneshot, +}; -pub struct Receivers { - pub room_receiver: RefCell>, +pub struct Requester { + worker_tasks_sender: UnboundedSender, } -impl Clone for Receivers { + +impl Clone for Requester { fn clone(&self) -> Self { Self { - room_receiver: RefCell::new(self.room_receiver.borrow().resubscribe()), + worker_tasks_sender: self.worker_tasks_sender.clone(), } } } -impl PartialEq for Receivers { - fn eq(&self, other: &Self) -> bool { - self.room_receiver - .borrow() - .same_channel(&other.room_receiver.borrow()) + +impl Requester { + pub fn new(worker_tasks_sender: UnboundedSender) -> Self { + Self { + worker_tasks_sender, + } } } -pub struct Requester { - pub matrix_client: Arc, - pub tx: UnboundedSender, - pub receivers: Receivers, +// TODO: Is there a way to avoid this duplication? +macro_rules! request_to_worker { + ($self:ident, $task:expr) => { + { + let (reply, mut response) = oneshot(); + + let task = $task(reply); + + if let Err(err) = $self.worker_tasks_sender.send(task) { + let msg = format!("Unable to request to the Matrix client: {err}"); + return Err(anyhow::Error::msg(msg)); + } + + match response.recv().await { + Some(result) => result, + None => Err(anyhow::Error::msg("TBD")), + } + } + }; + + ($self:ident, $task:expr $(, $arg:expr)+) => { + { + let (reply, mut response) = oneshot(); + + let task = $task($($arg),*, reply); + + if let Err(err) = $self.worker_tasks_sender.send(task) { + let msg = format!("Unable to request to the Matrix client: {err}"); + return Err(anyhow::Error::msg(msg)); + } + + match response.recv().await { + Some(result) => result, + None => Err(anyhow::Error::msg("TBD")), + } + } + }; } impl Requester { pub async fn init(&self) -> anyhow::Result<()> { - let (reply, mut response) = oneshot(); - - if let Err(err) = self.tx.send(WorkerTask::Init(reply)) { - let msg = format!("Unable to request the init of the Matrix client: {err}"); - return Err(anyhow::Error::msg(msg)); - } - - match response.recv().await { - Some(result) => Ok(result), - None => Err(anyhow::Error::msg("TBD")), - } + request_to_worker!(self, WorkerTask::Init) } pub async fn login(&self, style: LoginStyle) -> anyhow::Result<()> { - let (reply, mut response) = oneshot(); + request_to_worker!(self, WorkerTask::Login, style) + } +} - if let Err(err) = self.tx.send(WorkerTask::Login(style, reply)) { +#[async_trait(?Send)] +impl AccountMessagingProviderInterface for Requester { + async fn get_display_name(&self) -> anyhow::Result> { + request_to_worker!(self, WorkerTask::GetDisplayName) + } + + async fn get_avatar(&self) -> anyhow::Result> { + request_to_worker!(self, WorkerTask::GetAvatar) + } + + async fn run_forever( + &self, + account_events_consumer: &dyn AccountMessagingConsumerInterface, + mut account_events_receiver: Receiver, + ) -> anyhow::Result<()> { + // TODO: manage the result provided by response + let (run_forever_tx, _run_forever_rx) = oneshot(); + + if let Err(err) = self + .worker_tasks_sender + .send(WorkerTask::RunForever(run_forever_tx)) + { let msg = format!("Unable to request login to the Matrix client: {err}"); return Err(anyhow::Error::msg(msg)); } - match response.recv().await { - Some(result) => result, - None => Err(anyhow::Error::msg("TBD")), + let mut rooms_events_streams = StreamMap::new(); + let mut spaces_events_streams = StreamMap::new(); + + let mut room_events_consumers = + HashMap::>::new(); + let mut space_events_consumers = + HashMap::>::new(); + + // TODO: Fix this... + let client = Rc::new(self.clone()); + + loop { + select! { + res = account_events_receiver.recv() => { + if let Ok(account_event) = res { + match account_event { + AccountEvent::NewRoom(id, spaces, name, topic, is_direct, state, receiver, new_room_tx) => { + let mut room = Room::new(id, spaces, name, topic, is_direct, Some(state)); + let room_id = room.id().clone(); + + room.set_messaging_provider(client.clone()); + + let stream = BroadcastStream::new(receiver.into()); + rooms_events_streams.insert(room_id.clone(), stream); + + let room_events_consumer = account_events_consumer.on_new_room(room).await; + room_events_consumers.insert(room_id, room_events_consumer); + + // We're now ready to recv and compute RoomEvent. + new_room_tx.send(true).await; + }, + AccountEvent::NewSpace(id, name, topic, receiver, new_space_tx) => { + let mut space = Space::new(id, name, topic); + let space_id = space.id().clone(); + + space.set_messaging_provider(client.clone()); + + let stream = BroadcastStream::new(receiver.into()); + spaces_events_streams.insert(space_id.clone(), stream); + + let space_events_consumer = account_events_consumer.on_new_space(space).await; + space_events_consumers.insert(space_id, space_events_consumer); + + // We're now ready to recv and compute SpaceEvent. + new_space_tx.send(true).await; + } + }; + } + }, + Some((room_id, room_event)) = rooms_events_streams.next() => { + if let Ok(room_event) = room_event { + if let Some(consumer) = room_events_consumers.get(&room_id) { + match room_event { + RoomEvent::Invitation() => { + consumer.on_invitation().await; + }, + // RoomEvent::Membership(user_id, is_account_user) => { + // let member = RoomMember::new(UserId::from(user_id), room_id, is_account_user); + // consumer.on_membership(member).await; + // }, + RoomEvent::NewTopic(topic) => { + consumer.on_new_topic(topic).await; + }, + RoomEvent::NewName(name) => { + consumer.on_new_name(name).await; + }, + _ => {} + } + } else { + error!("No consumer found for \"{}\" room", room_id); + } + } + }, + Some((space_id, room_event)) = spaces_events_streams.next() => { + if let Ok(room_event) = room_event { + if let Some(consumer) = space_events_consumers.get(&space_id) { + match room_event { + RoomEvent::NewTopic(topic) => { + consumer.on_new_topic(topic).await; + }, + RoomEvent::NewName(name) => { + consumer.on_new_name(name).await; + }, + RoomEvent::NewChild(child_id) => { + consumer.on_child(child_id).await; + }, + _ => {} + } + } else { + error!("No consumer found for \"{}\" space", space_id); + } + } + } + } } } } + +#[async_trait(?Send)] +impl RoomMessagingProviderInterface for Requester { + async fn get_avatar(&self, room_id: &RoomId) -> anyhow::Result> { + request_to_worker!(self, WorkerTask::GetRoomAvatar, room_id.clone()) + } + + // TODO: Fix return code + async fn get_members(&self, room_id: &RoomId) -> anyhow::Result> { + match request_to_worker!(self, WorkerTask::GetRoomMembers, room_id.clone()) { + Ok(matrix_room_members) => { + Ok(join_all(matrix_room_members.iter().map(|member| async { + RoomMember::from_matrix(member, room_id, Rc::new(self.clone())).await + })) + .await) + } + Err(err) => Err(err), + } + } +} + +#[async_trait(?Send)] +impl SpaceMessagingProviderInterface for Requester {} + +#[async_trait(?Send)] +impl MemberMessagingProviderInterface for Requester { + async fn get_avatar( + &self, + room_id: &RoomId, + user_id: &UserId, + ) -> anyhow::Result> { + request_to_worker!( + self, + WorkerTask::GetRoomMemberAvatar, + room_id.clone(), + user_id.clone() + ) + } +} From bc6b02bc3422deb216f0f6a9e4be5b1a32a0a828 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 May 2024 22:32:35 +0200 Subject: [PATCH 18/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Rework=20the=20Ma?= =?UTF-8?q?trix=20messaging=20Client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/messaging/matrix/client.rs | 594 ++++++++++++------ 1 file changed, 419 insertions(+), 175 deletions(-) diff --git a/src/infrastructure/messaging/matrix/client.rs b/src/infrastructure/messaging/matrix/client.rs index 5b0dc34..295b909 100644 --- a/src/infrastructure/messaging/matrix/client.rs +++ b/src/infrastructure/messaging/matrix/client.rs @@ -1,37 +1,46 @@ -use std::borrow::Borrow; -use std::cell::RefCell; -use std::sync::Arc; -use std::time::Duration; +use std::{ + borrow::Borrow, + collections::HashMap, + sync::{Arc, Mutex}, +}; -use async_std::task; +use async_std::stream::StreamExt; use dioxus::prelude::Task; -use log::{debug, error}; -use tokio::sync::broadcast; -use tokio::sync::broadcast::Sender; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; -use tokio::sync::oneshot; -use tokio::task::JoinHandle; - use matrix_sdk::{ config::SyncSettings, event_handler::Ctx, - room::Room as MatrixRoom, + media::{MediaFormat, MediaThumbnailSize}, + room::{Room, RoomMember}, ruma::{ + api::client::media::get_content_thumbnail::v3::Method, events::{ room::{ - member::{RoomMemberEventContent, StrippedRoomMemberEvent}, + member::{ + OriginalSyncRoomMemberEvent, RoomMemberEventContent, StrippedRoomMemberEvent, + }, topic::RoomTopicEventContent, }, SyncStateEvent, }, - OwnedRoomId, + uint, OwnedRoomId, RoomId, UserId, }, - Client as MatrixClient, RoomState as MatrixRoomState, + Client as MatrixClient, RoomMemberships, RoomState, }; -use super::requester::{Receivers, Requester}; -use super::worker_tasks::{LoginStyle, WorkerTask}; -use crate::domain::model::room::Room; +use tokio::sync::{ + broadcast, + broadcast::{error::SendError, Receiver, Sender}, + mpsc::{unbounded_channel, UnboundedReceiver}, +}; +use tracing::{debug, error, warn}; + +use super::{ + account_event::AccountEvent, + requester::Requester, + room_event::{RoomEvent, RoomEventsReceiver}, + worker_tasks::{LoginStyle, WorkerTask}, +}; +use crate::utils::oneshot; #[derive(thiserror::Error, Debug)] pub enum ClientError { @@ -39,21 +48,45 @@ pub enum ClientError { Matrix(#[from] matrix_sdk::Error), } -#[derive(Clone)] -pub enum RoomEvent { - TopicEvent(OwnedRoomId, String), - MemberEvent(OwnedRoomId, Room), - InviteEvent(OwnedRoomId, Room), -} - #[derive(Clone)] struct Senders { - room_events_sender: Sender, + account_events_sender: Sender, + room_events_senders: Arc>>>, } impl Senders { - fn new(room_events_sender: Sender) -> Self { - Self { room_events_sender } + fn new(account_events_sender: Sender) -> Self { + Self { + account_events_sender, + room_events_senders: Arc::new(Mutex::new(HashMap::new())), + } + } + + fn contains(&self, room_id: &RoomId) -> bool { + let room_senders = self.room_events_senders.lock().unwrap(); + + room_senders.contains_key(room_id) + } + + fn send(&self, room_id: &RoomId, event: RoomEvent) -> Result> { + let room_senders = self.room_events_senders.lock().unwrap(); + if let Some(room_sender) = room_senders.get(room_id) { + room_sender.send(event) + } else { + error!("No sender found for \"{}\" room id", room_id); + Ok(0) + } + } + + fn add_room(&self, room_id: &OwnedRoomId) -> Option { + let mut senders = self.room_events_senders.lock().unwrap(); + if !senders.contains_key(room_id) { + let (room_sender, room_receiver) = broadcast::channel(32); + senders.insert(room_id.clone(), room_sender); + Some(RoomEventsReceiver::new(room_receiver)) + } else { + None + } } } @@ -65,16 +98,173 @@ pub struct Client { } impl Client { - pub fn new(client: Arc, room_events_sender: Sender) -> Self { + pub fn new(client: Arc, account_events_sender: Sender) -> Self { Self { initialized: false, client: Some(client), sync_task: None, - senders: Senders::new(room_events_sender), + senders: Senders::new(account_events_sender), } } - // async fn on_sync_typing_event(_ev: SyncTypingEvent, room: MatrixRoom) { + async fn create_space( + senders: &Ctx, + room_id: OwnedRoomId, + room: Option<&Room>, + ) -> anyhow::Result<(), SendError> { + let mut name = None; + let mut topic = None; + + if let Some(room) = room { + name = room.name(); + topic = room.topic(); + } + + if let Some(receiver) = senders.add_room(&room_id) { + let (reply, mut response) = oneshot::(); + + let event = AccountEvent::NewSpace(room_id.clone(), name, topic, receiver, reply); + + if let Err(err) = senders.account_events_sender.send(event) { + error!( + "Unable to publish the new room with \"{}\" id: {}", + room_id, err + ); + return Err(err); + } + + // We're expecting a response indicating that the client is able to compute the next RoomEvent + response.recv().await; + } else { + let events = vec![RoomEvent::NewTopic(topic), RoomEvent::NewName(name)]; + + for event in events { + if let Err(err) = senders.send(&room_id, event.clone()) { + error!( + "Unable to publish the {:?} event to the \"{}\" room: {}", + event, room_id, err + ); + // return Err(err); + } + } + } + Ok(()) + } + + async fn create_room( + senders: &Ctx, + room: &Room, + ) -> anyhow::Result<(), SendError> { + let room_id = room.room_id().to_owned(); + + if let Some(receiver) = senders.add_room(&room_id) { + let (reply, mut response) = oneshot::(); + + let is_direct = match room.is_direct().await { + Ok(is_direct) => Some(is_direct), + Err(err) => { + error!("Unable to know if the room \"{room_id}\" is direct: {err}"); + None + } + }; + + let mut parents = vec![]; + // TODO: Remove unwrap + let mut spaces = room.parent_spaces().await.unwrap(); + while let Some(parent) = spaces.next().await { + match parent { + Ok(parent) => match parent { + matrix_sdk::room::ParentSpace::Reciprocal(parent) => { + parents.push(parent.room_id().to_owned()); + } + _ => todo!(), + }, + Err(err) => { + error!("{}", err); + } + } + } + + let event = AccountEvent::NewRoom( + room_id.clone(), + parents.clone(), + room.name(), + room.topic(), + is_direct, + room.state(), + receiver, + reply, + ); + + if let Err(err) = senders.account_events_sender.send(event) { + error!( + "Unable to publish the new room with \"{}\" id: {}", + room.room_id(), + err + ); + return Err(err); + } + + // We're expecting a response indicating that the client is able to compute the next RoomEvent + response.recv().await; + } + Ok(()) + } + + async fn add_room( + senders: &Ctx, + room: &Room, + ) -> anyhow::Result<(), SendError> { + let room_id = room.room_id().to_owned(); + + if room.is_space() { + Self::create_space(senders, room_id, Some(room)).await + } else { + let ret = Self::create_room(senders, room).await; + + let mut parents = vec![]; + // TODO: Remove unwrap + let mut spaces = room.parent_spaces().await.unwrap(); + while let Some(parent) = spaces.next().await { + match parent { + Ok(parent) => match parent { + matrix_sdk::room::ParentSpace::Reciprocal(parent) => { + parents.push(parent.room_id().to_owned()); + } + _ => { + warn!( + "Only ParentSpace::Reciprocal taken into account, skip {:?}", + parent + ); + } + }, + Err(err) => { + error!("{}", err); + } + } + } + error!("parents={:?}", &parents); + + for parent in parents { + // Create a minimal space to make the relation consistent... its content will be sync later. + if !senders.contains(&parent) { + let _ = Self::create_space(senders, parent.clone(), None).await; + } + + let event = RoomEvent::NewChild(room_id.clone()); + if let Err(err) = senders.send(parent.as_ref(), event.clone()) { + error!( + "Unable to send the {:?} event to the \"{}\": {:?}", + event, parent, err + ); + } + } + + ret + } + } + + // async fn on_sync_typing_event(_ev: SyncTypingEvent, room: Room) { // debug!("== on_sync_typing_event =="); // let room_id = room.room_id().to_owned(); // dbg!(room_id); @@ -85,7 +275,7 @@ impl Client { // dbg!(_ev); // } - // async fn on_sync_state_event(ev: SyncStateEvent, _room: MatrixRoom) { + // async fn on_sync_state_event(ev: SyncStateEvent, _room: Room) { // error!("== on_sync_state_event =="); // if let SyncStateEvent::Original(ev) = ev { // dbg!(ev); @@ -94,32 +284,33 @@ impl Client { // async fn on_original_sync_room_message_event( // ev: OriginalSyncRoomMessageEvent, - // _matrix_room: MatrixRoom, + // _room: Room, // _senders: Ctx, // ) { // error!("== on_original_sync_room_message_event =="); // error!("ev={:?}", ev.content); - // } async fn on_stripped_room_member_event( ev: StrippedRoomMemberEvent, matrix_client: MatrixClient, - matrix_room: MatrixRoom, + room: Room, senders: Ctx, ) { - if ev.state_key == matrix_client.user_id().unwrap() - && matrix_room.state() == MatrixRoomState::Invited - { - let room_id = matrix_room.room_id(); - let room = Room::from_matrix_room(&matrix_room).await; + error!("*** on_stripped_room_member_event ***"); + // error!("ev={:?}", ev); - if let Err(err) = senders - .room_events_sender - .send(RoomEvent::InviteEvent(room_id.to_owned(), room)) - { + if ev.state_key == matrix_client.user_id().unwrap() + && room.state() == RoomState::Invited + && Self::add_room(&senders, &room).await.is_ok() + { + let room_id = room.room_id(); + + let event = RoomEvent::Invitation(); + if let Err(err) = senders.send(room_id, event) { error!( - "Unable to publish the new room with \"{}\" id: {}", - room_id, err + "Unable to publish the room \"{}\" invitation: {}", + room.room_id(), + err ); } } @@ -127,45 +318,56 @@ impl Client { async fn on_room_topic_event( ev: SyncStateEvent, - matrix_room: MatrixRoom, + room: Room, senders: Ctx, ) { - if let SyncStateEvent::Original(ev) = ev { - let room_id = matrix_room.room_id(); + error!("*** on_room_topic_event ***"); + // error!("ev={:?}", ev); - if let Err(err) = senders - .room_events_sender - .send(RoomEvent::TopicEvent(room_id.to_owned(), ev.content.topic)) - { - error!("Unable to publish the \"{}\" new topic: {}", room_id, err); + if let SyncStateEvent::Original(ev) = ev { + let _ = Self::add_room(&senders, &room).await; + + let room_id = room.room_id(); + let event = RoomEvent::NewTopic(Some(ev.content.topic)); + if let Err(err) = senders.send(room_id, event) { + error!( + "Unable to publish the room \"{}\" topic: {}", + room.room_id(), + err + ); } } } async fn on_room_member_event( ev: SyncStateEvent, - matrix_room: MatrixRoom, + room: Room, senders: Ctx, ) { - if let SyncStateEvent::Original(_ev) = ev { - let room_id = matrix_room.room_id(); - let room = Room::from_matrix_room(&matrix_room).await; + error!("*** on_room_member_event ***"); + // error!("ev={:?}", ev); - if let Err(err) = senders - .room_events_sender - .send(RoomEvent::MemberEvent(room_id.to_owned(), room)) - { - error!( - "Unable to publish the new room with \"{}\" id: {}", - room_id, err - ); + if let SyncStateEvent::Original(_ev) = ev { + if Self::add_room(&senders, &room).await.is_ok() { + // let room_id = room.room_id(); + // // TODO: Client shall only manage Matrix object... not BG92 ones. + // let event = RoomEvent::Membership(RoomMember::new(ev.sender, room_id)); + // if let Some(result) = senders.send(room_id, event) { + // if let Err(err) = result { + // error!( + // "Unable to publish the room \"{}\" membership: {}", + // room.room_id(), + // err + // ); + // } + // } } } } // async fn on_sync_message_like_room_message_event( // ev: SyncMessageLikeEvent, - // _room: MatrixRoom, + // _room: Room, // _client: MatrixClient, // ) { // debug!("== on_sync_message_like_room_message_event =="); @@ -174,7 +376,7 @@ impl Client { // async fn on_sync_message_like_reaction_event( // ev: SyncMessageLikeEvent, - // _room: MatrixRoom, + // _room: Room, // ) { // debug!("== on_sync_message_like_reaction_event =="); // dbg!(ev); @@ -182,28 +384,28 @@ impl Client { // async fn on_original_sync_room_redaction_event( // ev: OriginalSyncRoomRedactionEvent, - // _room: MatrixRoom, + // _room: Room, // ) { // debug!("== on_original_sync_room_redaction_event =="); // dbg!(ev); // } - // async fn on_original_sync_room_member_event( - // _ev: OriginalSyncRoomMemberEvent, - // _room: MatrixRoom, - // _client: MatrixClient, - // ) { - // debug!("== on_original_sync_room_member_event =="); - - // 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_room_member_event( + _ev: OriginalSyncRoomMemberEvent, + _room: Room, + _client: MatrixClient, + ) { + // debug!("== on_original_sync_room_member_event =="); + // error!("room={:?}", room); + // 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, @@ -265,11 +467,7 @@ impl Client { // debug!("== on_room_event({}) ==", ev.) // } - pub async fn spawn(homeserver_url: String) -> Requester { - let (tx, rx) = unbounded_channel::(); - - let (room_sender, room_receiver) = broadcast::channel(32); - + pub async fn spawn(homeserver_url: String) -> (Requester, Receiver) { let matrix_client = Arc::new( MatrixClient::builder() .homeserver_url(&homeserver_url) @@ -278,23 +476,22 @@ impl Client { .unwrap(), ); - let mut client = Client::new(matrix_client.clone(), room_sender); + let (worker_tasks_sender, worker_tasks_receiver) = unbounded_channel::(); + let (account_events_sender, account_events_receiver) = + broadcast::channel::(32); + + let mut client = Client::new(matrix_client, account_events_sender); dioxus::prelude::spawn(async move { - client.work(rx).await; + client.work(worker_tasks_receiver).await; }); - Requester { - matrix_client, - tx, - receivers: Receivers { - room_receiver: RefCell::new(room_receiver), - }, - } + (Requester::new(worker_tasks_sender), account_events_receiver) } fn init(&mut self) { if let Some(client) = self.client.borrow() { + // TODO: Remove clone? client.add_event_handler_context(self.senders.clone()); let _ = client.add_event_handler(Client::on_stripped_room_member_event); @@ -309,7 +506,9 @@ impl Client { // let _ = client.add_event_handler(Client::on_sync_message_like_room_message_event); // let _ = client.add_event_handler(Client::on_sync_message_like_reaction_event); // let _ = client.add_event_handler(Client::on_original_sync_room_redaction_event); - // let _ = client.add_event_handler(Client::on_original_sync_room_member_event); + + let _ = client.add_event_handler(Client::on_original_sync_room_member_event); + // let _ = client.add_event_handler(Client::on_original_sync_key_verif_start_event); // let _ = client.add_event_handler(Client::on_original_sync_key_verif_key_event); // let _ = client.add_event_handler(Client::on_original_sync_key_verif_done_event); @@ -322,12 +521,12 @@ impl Client { } } - async fn login_and_sync(&mut self, style: LoginStyle) -> anyhow::Result<()> { - let client = self.client.clone().unwrap(); + async fn login(&mut self, style: LoginStyle) -> anyhow::Result<()> { + let client = self.client.as_ref().unwrap(); match style { LoginStyle::Password(username, password) => { - let _resp = client + client .matrix_auth() .login_username(&username, &password) .initial_device_display_name("TODO") @@ -337,7 +536,11 @@ impl Client { } } - let (synchronized_tx, synchronized_rx) = oneshot::channel::(); + Ok(()) + } + + async fn run_forever(&mut self) { + let client = self.client.clone().unwrap(); let task = dioxus::prelude::spawn(async move { // Sync once so we receive the client state and old messages @@ -350,87 +553,107 @@ impl Client { }; if let Some(sync_token) = sync_token_option { - let settings = SyncSettings::default().token(sync_token); - debug!("User connected to the homeserver, start syncing"); - if let Err(err) = synchronized_tx.send(true) { - error!("Unable to notify that the Matrix client is now synchronized ({err})"); - } - + let settings = SyncSettings::default().token(sync_token); let _ = client.sync(settings).await; } }); self.sync_task = Some(task); - - // self.start_background_tasks(synchronized_rx); - - Ok(()) } - // async fn register_room_events(&self, room_id: OwnedRoomId) { - // let client = self.client.unwrap(); + async fn get_display_name(&mut self) -> anyhow::Result> { + let client = self.client.as_ref().unwrap(); - // client.add_room_event_handler(&room_id, Client::on_room_event); - // } + match client.account().get_display_name().await { + Ok(display_name) => Ok(display_name), + Err(err) => Err(err.into()), + } + } - // async fn refresh_rooms( - // matrix_client: &Arc, - // room_events_sender: &Sender, - // ) { - // let joined_matrix_rooms_ref = &matrix_client.joined_rooms(); - // let invited_matrix_rooms_ref = &matrix_client.invited_rooms(); + async fn get_avatar(&mut self) -> anyhow::Result>> { + let client = self.client.as_ref().unwrap(); - // for matrix_rooms in [joined_matrix_rooms_ref, invited_matrix_rooms_ref] { - // for matrix_room in matrix_rooms.iter() { - // let room = Room::from_matrix_room(matrix_room).await; - // let event = RoomEvent::MemberEvent(room.id().clone(), room); + match client + .account() + .get_avatar(MediaFormat::Thumbnail(MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + })) + .await + { + Ok(avatar) => Ok(avatar), + Err(err) => Err(err.into()), + } + } - // if let Err(err) = room_events_sender.send(event) { - // error!("Error: {}", err); - // } - // } - // } - // } + async fn get_room_avatar(&mut self, room_id: &OwnedRoomId) -> anyhow::Result>> { + let client = self.client.as_ref().unwrap(); - // async fn refresh_rooms_forever( - // matrix_client: Arc, - // room_events_sender: &Sender, - // ) { - // // TODO: Add interval to config - // let period_sec = Duration::from_secs(5); + if let Some(room) = client.get_room(room_id) { + match room + .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + })) + .await + { + Ok(avatar) => Ok(avatar), + Err(err) => Err(err.into()), + } + } else { + error!("No room found with the \"{}\" id", room_id.as_str()); + // TODO: Return an error if the room has not been found + Ok(None) + } + } - // loop { - // Self::refresh_rooms(&matrix_client, room_events_sender).await; + async fn get_room_members(&mut self, room_id: &OwnedRoomId) -> anyhow::Result> { + let client = self.client.as_ref().unwrap(); - // task::sleep(period_sec).await; - // } - // } + if let Some(room) = client.get_room(room_id) { + match room.members(RoomMemberships::ACTIVE).await { + Ok(room_members) => Ok(room_members), + Err(err) => Err(err.into()), + } + } else { + error!("No room found with the \"{}\" id", room_id.as_str()); + // TODO: Return an error if the room has not been found + Ok(vec![]) + } + } - // fn start_background_tasks(&mut self, synchronized_rx: oneshot::Receiver) { - // let client = self.client.clone().unwrap(); - // let room_events_sender = self.senders.room_events_sender.clone(); + async fn get_room_member_avatar( + &self, + room_id: &RoomId, + user_id: &UserId, + ) -> anyhow::Result>> { + let client = self.client.as_ref().unwrap(); - // let task = dioxus::prelude::spawn(async move { - // if let Err(err) = synchronized_rx.await { - // error!("Unable to setup the rx channel notifying that the Matrix client is now synchronized ({err})"); - // } - - // debug!("Start room refreshing forever"); - - // let _ = Self::refresh_rooms_forever(client, &room_events_sender).await; - // }); - // self.background_task = Some(task); - // } + if let Some(room) = client.get_room(room_id) { + if let Ok(Some(room_member)) = room.get_member(user_id).await { + let res = match room_member + .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + })) + .await + { + Ok(avatar) => Ok(avatar), + Err(err) => Err(err.into()), + }; + return res; + } + } + Ok(None) + } async fn work(&mut self, mut rx: UnboundedReceiver) { - loop { - match rx.recv().await { - Some(task) => self.run(task).await, - None => { - break; - } - } + while let Some(task) = rx.recv().await { + self.run(task).await; } if let Some(task) = self.sync_task.take() { @@ -441,17 +664,38 @@ impl Client { async fn run(&mut self, task: WorkerTask) { match task { WorkerTask::Init(reply) => { - assert!(!self.initialized); self.init(); - reply.send(()).await; + reply.send(Ok(())).await; + } + WorkerTask::RunForever(reply) => { + { + self.run_forever().await; + reply.send(()) + } + .await } WorkerTask::Login(style, reply) => { - assert!(self.initialized); - reply.send(self.login_and_sync(style).await).await; - } // WorkerTask::registerRoomEvents(room_id, reply) => { - // assert!(self.initialized); - // reply.send(self.register_room_events(room_id).await).await; - // } + reply.send(self.login(style).await).await; + } + WorkerTask::GetDisplayName(reply) => { + reply.send(self.get_display_name().await).await; + } + WorkerTask::GetAvatar(reply) => { + reply.send(self.get_avatar().await).await; + } + + WorkerTask::GetRoomAvatar(id, reply) => { + reply.send(self.get_room_avatar(&id).await).await; + } + WorkerTask::GetRoomMembers(id, reply) => { + reply.send(self.get_room_members(&id).await).await; + } + + WorkerTask::GetRoomMemberAvatar(room_id, user_id, reply) => { + reply + .send(self.get_room_member_avatar(&room_id, &user_id).await) + .await; + } } } } From 18a797bc3fe2fd32c4cdaa50479684c6067c887b Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 11 May 2024 15:24:49 +0200 Subject: [PATCH 19/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20Account,=20Roo?= =?UTF-8?q?m=20and=20Space=20UI=20store=20structs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 + src/base.rs | 183 +++--------------- src/domain/model/account.rs | 18 +- src/domain/model/messaging_interface.rs | 4 +- src/domain/model/room.rs | 10 +- src/domain/model/space.rs | 8 +- src/domain/model/store_interface.rs | 10 +- .../messaging/matrix/requester.rs | 4 + src/ui/mod.rs | 1 + src/ui/store/mod.rs | 50 +++++ src/ui/store/room.rs | 21 ++ src/ui/store/space.rs | 24 +++ 12 files changed, 152 insertions(+), 183 deletions(-) create mode 100644 src/ui/store/mod.rs create mode 100644 src/ui/store/room.rs create mode 100644 src/ui/store/space.rs diff --git a/Cargo.toml b/Cargo.toml index a00b679..30bc930 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ web = ["dioxus/web"] dioxus = "0.5.*" dioxus-sdk = { version = "0.5.*", features = ["utils"] } dioxus-free-icons = { version = "0.8.*", features = ["ionicons", "font-awesome-solid", "material-design-icons-navigation"] } +# modx = "0.1" +modx = { git = "https://github.com/ASR-ASU/modx.git", branch = "asr/fix-props-visibility" } # matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", branch = "main", default-features = false, features = ["js", "rustls-tls"] } matrix-sdk = { version = "0.7.*", default-features = false, features = ["js", "rustls-tls"] } diff --git a/src/base.rs b/src/base.rs index 515beca..c906966 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,185 +1,60 @@ -// Cf. https://dioxuslabs.com/learn/0.4/reference/use_coroutine -// In order to use/run the rx.next().await statement you will need to extend the [Stream] trait -// (used by [UnboundedReceiver]) by adding 'futures_util' as a dependency to your project -// and adding the use futures_util::stream::StreamExt; -use std::cell::RefCell; +use std::rc::Rc; use dioxus::prelude::*; use futures_util::stream::StreamExt; use log::{debug, error, warn}; -use matrix_sdk::ruma::OwnedRoomId; -use tokio::select; -use crate::domain::model::room::{ByIdRooms, Room}; +use crate::domain::model::account::Account; +use crate::domain::model::messaging_interface::AccountMessagingProviderInterface; use crate::domain::model::session::Session; -use crate::infrastructure::messaging::matrix::client::{Client, RoomEvent}; -use crate::infrastructure::messaging::matrix::requester::{Receivers, Requester}; +use crate::infrastructure::messaging::matrix::client::Client; use crate::infrastructure::messaging::matrix::worker_tasks::LoginStyle; -use crate::ui::components::chats_window::interface::Interface as ChatsWinInterface; +use crate::ui::store::Store; -// #[derive(Clone, Debug)] -// pub struct UserInfo { -// pub avatar_url: Option, -// pub display_name: Option, -// pub blurhash: Option, -// } - -// impl UserInfo { -// pub fn new( -// avatar_url: Option, -// display_name: Option, -// blurhash: Option, -// ) -> Self { -// Self { -// avatar_url, -// display_name, -// blurhash, -// } -// } -// } - -// pub type ByIdUserInfos = HashMap; - -// #[derive(Clone)] -// pub struct Store { -// pub is_logged: bool, -// pub rooms: ByIdRooms, -// pub user_infos: ByIdUserInfos, -// pub user_id: Option, -// } - -// impl 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 struct AppSettings { - pub requester: Option>, -} - -impl AppSettings { - pub fn new() -> Self { - Self { requester: None } - } - - pub fn set_requester(&mut self, requester: RefCell) { - self.requester = Some(requester); - } -} - -async fn on_room(room_id: OwnedRoomId, room: Room, by_id_rooms: &GlobalSignal) { - // TODO: Update rooms - by_id_rooms - .write() - .insert(room_id, RefCell::::new(room)); -} - -async fn on_joining_invitation( - room_id: OwnedRoomId, - room: Room, - by_id_rooms: &GlobalSignal, -) { - debug!("You're invited to join the \"{}\" room", room.id()); - // TODO: Update rooms - by_id_rooms - .write() - .insert(room_id, RefCell::::new(room)); -} - -async fn on_room_topic(room_id: OwnedRoomId, topic: String, by_id_rooms: &GlobalSignal) { - if let Some(room) = by_id_rooms.read().get(&room_id) { - let mut room = room.borrow_mut(); - room.set_topic(Some(topic)); - } else { - warn!("No room found with the \"{}\" id", room_id); - } -} - -pub async fn sync_messages(by_id_rooms: &GlobalSignal, room_id: OwnedRoomId) { - error!("== sync_messages =="); - -} - -pub async fn sync_rooms( - mut rx: UnboundedReceiver, - receivers: Receivers, - by_id_rooms: &GlobalSignal, -) { - if let Some(_is_logged) = rx.next().await { - let mut rooms_receiver = receivers.room_receiver.borrow_mut(); - - loop { - // TODO: Remove select if no more receivers will be used. - select! { - res = rooms_receiver.recv() => { - if let Ok(room_event) = res { - match room_event { - RoomEvent::MemberEvent(room_id, room) => on_room(room_id, room, &by_id_rooms).await, - RoomEvent::InviteEvent(room_id, room) => on_joining_invitation(room_id, room, &by_id_rooms).await, - RoomEvent::TopicEvent(room_id, topic) => on_room_topic(room_id, topic, &by_id_rooms).await, - }; - } - }, - } - } - } -} - -pub async fn login( - mut rx: UnboundedReceiver, - app_settings: &GlobalSignal, - session: &GlobalSignal, -) { +pub async fn login(mut rx: UnboundedReceiver, session: &GlobalSignal) { while let Some(is_logged) = rx.next().await { + error!("is_logged={is_logged}"); if !is_logged { let homeserver_url = session.read().homeserver_url.clone(); let username = session.read().username.clone(); let password = session.read().password.clone(); if homeserver_url.is_some() && username.is_some() && password.is_some() { - let client = Client::spawn(homeserver_url.unwrap()).await; + let (requester, account_events_receiver) = + Client::spawn(homeserver_url.unwrap()).await; - if let Err(err) = client.init().await { + if let Err(err) = requester.init().await { error!("Following error occureds during client init: {}", err); } - match client + error!("Before login"); + + match requester .login(LoginStyle::Password(username.unwrap(), password.unwrap())) .await { Ok(_) => { debug!("successfully logged"); session.write().is_logged = true; + + let requester = Rc::new(requester); + + dioxus::prelude::spawn(async move { + // ACCOUNT.write().set_messaging_provider(requester.clone()); + ACCOUNT.write().set_messaging_provider(requester.clone()); + + let _ = requester + .run_forever(&*ACCOUNT.read(), account_events_receiver) + .await; + }); } Err(err) => { error!("Error during login: {err}"); // TODO: Handle invalid login // invalid_login.modify(|_| true); + return; } } - app_settings.write().set_requester(RefCell::new(client)); } else { warn!("At least one of the following values is/are invalid: homeserver, username or password"); } @@ -190,8 +65,8 @@ pub async fn login( error!("=== LOGIN END ==="); } -pub static APP_SETTINGS: GlobalSignal = Signal::global(AppSettings::new); -pub static ROOMS: GlobalSignal = Signal::global(ByIdRooms::new); +pub static STORE: GlobalSignal = Signal::global(Store::new); + +// TODO: Merge ACCOUNT and SESSION +pub static ACCOUNT: GlobalSignal = Signal::global(|| Account::new(&STORE)); pub static SESSION: GlobalSignal = Signal::global(Session::new); -pub static CHATS_WIN_INTERFACE: GlobalSignal = - Signal::global(ChatsWinInterface::new); diff --git a/src/domain/model/account.rs b/src/domain/model/account.rs index ebd4299..c1b81f5 100644 --- a/src/domain/model/account.rs +++ b/src/domain/model/account.rs @@ -1,6 +1,4 @@ -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use async_trait::async_trait; use tracing::error; @@ -95,32 +93,30 @@ impl Account { #[async_trait(?Send)] impl AccountMessagingConsumerInterface for Account { - async fn on_new_room(&self, room: Room) -> Rc { + async fn on_new_room(&self, room: Rc) -> Rc { let room_id = room.id().clone(); - let room = Rc::new(room); self.by_id_rooms .borrow_mut() .insert(room_id, Rc::clone(&room)); - let room_store = Box::new(self.store.on_new_room(Rc::clone(&room))); + let room_store = self.store.on_new_room(Rc::clone(&room)); - room.set_store(Some(room_store)); + room.set_store(room_store); room } - async fn on_new_space(&self, space: Space) -> Rc { + async fn on_new_space(&self, space: Rc) -> Rc { let space_id = space.id().clone(); - let space = Rc::new(space); self.by_id_spaces .borrow_mut() .insert(space_id, Rc::clone(&space)); - let space_store = Box::new(self.store.on_new_space(Rc::clone(&space))); + let space_store = self.store.on_new_space(Rc::clone(&space)); - space.set_store(Some(space_store)); + space.set_store(space_store); space } diff --git a/src/domain/model/messaging_interface.rs b/src/domain/model/messaging_interface.rs index fd8183b..9a1fb60 100644 --- a/src/domain/model/messaging_interface.rs +++ b/src/domain/model/messaging_interface.rs @@ -13,8 +13,8 @@ use crate::infrastructure::messaging::matrix::account_event::AccountEvent; #[async_trait(?Send)] pub trait AccountMessagingConsumerInterface { - async fn on_new_room(&self, room: Room) -> Rc; - async fn on_new_space(&self, space: Space) -> Rc; + async fn on_new_room(&self, room: Rc) -> Rc; + async fn on_new_space(&self, space: Rc) -> Rc; } #[async_trait(?Send)] diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index d7c6de9..bd16e76 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -1,6 +1,4 @@ -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use async_trait::async_trait; use futures::future::{join, join_all}; @@ -34,7 +32,7 @@ pub struct Room { members: RefCell>, messaging_provider: Option>, - store: RefCell>>, + store: RefCell>>, } impl PartialEq for Room { @@ -75,8 +73,8 @@ impl Room { self.messaging_provider = Some(messaging_provider); } - pub fn set_store(&self, store: Option>) { - *self.store.borrow_mut() = store; + pub fn set_store(&self, store: Rc) { + *self.store.borrow_mut() = Some(store); } pub fn id(&self) -> &RoomId { diff --git a/src/domain/model/space.rs b/src/domain/model/space.rs index 034fac5..3c8a4c1 100644 --- a/src/domain/model/space.rs +++ b/src/domain/model/space.rs @@ -26,7 +26,7 @@ pub struct Space { children: RefCell>, // We don´t expect to manage nested spaces messaging_provider: Option>, - store: RefCell>>, + store: RefCell>>, } impl PartialEq for Space { @@ -57,8 +57,8 @@ impl Space { self.messaging_provider = Some(provider); } - pub fn set_store(&self, store: Option>) { - *self.store.borrow_mut() = store; + pub fn set_store(&self, store: Rc) { + *self.store.borrow_mut() = Some(store); } pub fn id(&self) -> &SpaceId { @@ -84,7 +84,7 @@ impl SpaceMessagingConsumerInterface for Space { error!("Space::on_new_name({:?})", name); self.name.borrow_mut().clone_from(&name); - if let Some(store) = self.store.borrow_mut().as_mut() { + if let Some(store) = self.store.borrow().as_ref() { store.set_name(name); } } diff --git a/src/domain/model/store_interface.rs b/src/domain/model/store_interface.rs index 7a097a9..856d4b4 100644 --- a/src/domain/model/store_interface.rs +++ b/src/domain/model/store_interface.rs @@ -1,15 +1,13 @@ use std::rc::Rc; -use super::room::Room; -use super::space::Space; -use crate::base::{StoreRoom, StoreSpace}; +use super::{room::Room, space::Space}; #[allow(dead_code)] pub trait AccountStoreConsumerInterface {} pub trait AccountStoreProviderInterface { - fn on_new_room(&self, room: Rc) -> StoreRoom; - fn on_new_space(&self, space: Rc) -> StoreSpace; + fn on_new_room(&self, room: Rc) -> Rc; + fn on_new_space(&self, space: Rc) -> Rc; } #[allow(dead_code)] @@ -19,5 +17,5 @@ pub trait RoomStoreProviderInterface {} #[allow(dead_code)] pub trait SpaceStoreConsumerInterface {} pub trait SpaceStoreProviderInterface { - fn set_name(&mut self, _name: Option) {} + fn set_name(&self, _name: Option) {} } diff --git a/src/infrastructure/messaging/matrix/requester.rs b/src/infrastructure/messaging/matrix/requester.rs index 6e934de..a1307cd 100644 --- a/src/infrastructure/messaging/matrix/requester.rs +++ b/src/infrastructure/messaging/matrix/requester.rs @@ -147,6 +147,8 @@ impl AccountMessagingProviderInterface for Requester { room.set_messaging_provider(client.clone()); + let room = Rc::new(room); + let stream = BroadcastStream::new(receiver.into()); rooms_events_streams.insert(room_id.clone(), stream); @@ -162,6 +164,8 @@ impl AccountMessagingProviderInterface for Requester { space.set_messaging_provider(client.clone()); + let space = Rc::new(space); + let stream = BroadcastStream::new(receiver.into()); spaces_events_streams.insert(space_id.clone(), stream); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1cccdfe..3d84e88 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod components; +pub(crate) mod store; pub(crate) mod views; diff --git a/src/ui/store/mod.rs b/src/ui/store/mod.rs new file mode 100644 index 0000000..5771d96 --- /dev/null +++ b/src/ui/store/mod.rs @@ -0,0 +1,50 @@ +pub(crate) mod room; +pub(crate) mod space; + +use std::cell::RefCell; +use std::{collections::HashMap, rc::Rc}; + +use async_trait::async_trait; +use dioxus::prelude::*; + +use crate::domain::model::room::{Room as DomainRoom, RoomId}; +use crate::domain::model::space::{Space as DomainSpace, SpaceId}; +use crate::domain::model::store_interface::{ + AccountStoreProviderInterface, RoomStoreProviderInterface, SpaceStoreProviderInterface, +}; + +use room::{Room, RoomProps}; +use space::{Space, SpaceProps}; + +#[modx::store] +pub struct Store { + rooms: HashMap, + spaces: HashMap, +} + +#[async_trait(?Send)] +impl AccountStoreProviderInterface for GlobalSignal { + fn on_new_room(&self, domain_room: Rc) -> Rc { + let room_id = domain_room.id(); + + let props = RoomProps::new(room_id.clone(), domain_room.name()); + let room = Room::new(props); + + let mut rooms = self.read().rooms; + rooms.write().insert(room_id.clone(), room); + + Rc::new(room.clone()) + } + + fn on_new_space(&self, domain_space: Rc) -> Rc { + let space_id = domain_space.id(); + + let props = SpaceProps::new(space_id.clone(), domain_space.name()); + let space = Space::new(props); + + let mut spaces = self.read().spaces; + spaces.write().insert(space_id.clone(), space); + + Rc::new(RefCell::new(space.clone())) + } +} diff --git a/src/ui/store/room.rs b/src/ui/store/room.rs new file mode 100644 index 0000000..40bad22 --- /dev/null +++ b/src/ui/store/room.rs @@ -0,0 +1,21 @@ +use dioxus::prelude::*; + +use crate::domain::model::{ + room::RoomId, room_member::RoomMember, store_interface::RoomStoreProviderInterface, +}; + +#[modx::props(id, name)] +#[modx::store] +pub struct Room { + id: RoomId, + name: Option, + members: Vec, +} + +impl PartialEq for Room { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl RoomStoreProviderInterface for Room {} diff --git a/src/ui/store/space.rs b/src/ui/store/space.rs new file mode 100644 index 0000000..8ebaeb8 --- /dev/null +++ b/src/ui/store/space.rs @@ -0,0 +1,24 @@ +use std::cell::RefCell; + +use dioxus::prelude::*; + +use crate::domain::model::{space::SpaceId, store_interface::SpaceStoreProviderInterface}; + +#[modx::props(id, name)] +#[modx::store] +pub struct Space { + id: SpaceId, + name: Option, +} + +impl PartialEq for Space { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl SpaceStoreProviderInterface for RefCell { + fn set_name(&self, name: Option) { + self.borrow_mut().name.set(name); + } +} From bc30670f6ee1df96a49f41798712b7d89563c4de Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 15 May 2024 18:09:18 +0200 Subject: [PATCH 20/41] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Bumps=20modx=20ve?= =?UTF-8?q?rsion=20(git=20->=200.1.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 30bc930..d94127c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,7 @@ web = ["dioxus/web"] dioxus = "0.5.*" dioxus-sdk = { version = "0.5.*", features = ["utils"] } dioxus-free-icons = { version = "0.8.*", features = ["ionicons", "font-awesome-solid", "material-design-icons-navigation"] } -# modx = "0.1" -modx = { git = "https://github.com/ASR-ASU/modx.git", branch = "asr/fix-props-visibility" } +modx = "0.1.2" # matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", branch = "main", default-features = false, features = ["js", "rustls-tls"] } matrix-sdk = { version = "0.7.*", default-features = false, features = ["js", "rustls-tls"] } From d77c2a9d121710b725b9ad54967921e92d283e4a Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 15 May 2024 19:07:46 +0200 Subject: [PATCH 21/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Use=20of=20Store?= =?UTF-8?q?=20interfaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/account.rs | 12 ++++-- src/domain/model/room.rs | 14 +++++++ src/domain/model/space.rs | 12 +++++- src/domain/model/store_interface.rs | 37 ++++++++++++++--- src/ui/store/mod.rs | 42 +++++++++---------- src/ui/store/room.rs | 62 ++++++++++++++++++++++++++--- src/ui/store/space.rs | 42 +++++++++++++++---- 7 files changed, 178 insertions(+), 43 deletions(-) diff --git a/src/domain/model/account.rs b/src/domain/model/account.rs index c1b81f5..18e083a 100644 --- a/src/domain/model/account.rs +++ b/src/domain/model/account.rs @@ -11,7 +11,9 @@ use super::{ }, room::{Room, RoomId}, space::{Space, SpaceId}, - store_interface::AccountStoreProviderInterface, + store_interface::{ + AccountStoreProviderInterface, RoomStoreConsumerInterface, SpaceStoreConsumerInterface, + }, }; type Rooms = HashMap>; @@ -100,7 +102,9 @@ impl AccountMessagingConsumerInterface for Account { .borrow_mut() .insert(room_id, Rc::clone(&room)); - let room_store = self.store.on_new_room(Rc::clone(&room)); + let room_store = self + .store + .on_new_room(Rc::clone(&room) as Rc); room.set_store(room_store); @@ -114,7 +118,9 @@ impl AccountMessagingConsumerInterface for Account { .borrow_mut() .insert(space_id, Rc::clone(&space)); - let space_store = self.store.on_new_space(Rc::clone(&space)); + let space_store = self + .store + .on_new_space(Rc::clone(&space) as Rc); space.set_store(space_store); diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index bd16e76..53d757d 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -207,3 +207,17 @@ impl RoomMessagingConsumerInterface for Room { trace!("Room::on_new_name({:?})", name); } } + +impl RoomStoreConsumerInterface for Room { + fn id(&self) -> &RoomId { + &self.id + } + + fn is_direct(&self) -> Option { + self.is_direct + } + + fn name(&self) -> Option { + self.name.borrow().clone() + } +} diff --git a/src/domain/model/space.rs b/src/domain/model/space.rs index 3c8a4c1..b269c61 100644 --- a/src/domain/model/space.rs +++ b/src/domain/model/space.rs @@ -8,7 +8,7 @@ use super::{ common::Avatar, messaging_interface::{SpaceMessagingConsumerInterface, SpaceMessagingProviderInterface}, room::RoomId, - store_interface::SpaceStoreProviderInterface, + store_interface::{SpaceStoreConsumerInterface, SpaceStoreProviderInterface}, }; pub type SpaceId = OwnedRoomId; @@ -89,3 +89,13 @@ impl SpaceMessagingConsumerInterface for Space { } } } + +impl SpaceStoreConsumerInterface for Space { + fn id(&self) -> &SpaceId { + &self.id + } + + fn name(&self) -> Option { + self.name.borrow().clone() + } +} diff --git a/src/domain/model/store_interface.rs b/src/domain/model/store_interface.rs index 856d4b4..520f700 100644 --- a/src/domain/model/store_interface.rs +++ b/src/domain/model/store_interface.rs @@ -1,21 +1,46 @@ use std::rc::Rc; -use super::{room::Room, space::Space}; +use super::{ + room::{Invitation, RoomId}, + room_member::RoomMember, + space::SpaceId, +}; #[allow(dead_code)] pub trait AccountStoreConsumerInterface {} pub trait AccountStoreProviderInterface { - fn on_new_room(&self, room: Rc) -> Rc; - fn on_new_space(&self, space: Rc) -> Rc; + fn on_new_room( + &self, + room: Rc, + ) -> Rc; + fn on_new_space( + &self, + space: Rc, + ) -> Rc; } #[allow(dead_code)] -pub trait RoomStoreConsumerInterface {} -pub trait RoomStoreProviderInterface {} +pub trait RoomStoreConsumerInterface { + fn id(&self) -> &RoomId; + + fn is_direct(&self) -> Option; + fn name(&self) -> Option; +} + +pub trait RoomStoreProviderInterface { + fn on_new_member(&self, member: RoomMember); + fn on_new_name(&self, name: Option); + fn on_invitation(&self, invitation: Invitation); +} #[allow(dead_code)] -pub trait SpaceStoreConsumerInterface {} +pub trait SpaceStoreConsumerInterface { + fn id(&self) -> &SpaceId; + + fn name(&self) -> Option; +} + pub trait SpaceStoreProviderInterface { fn set_name(&self, _name: Option) {} } diff --git a/src/ui/store/mod.rs b/src/ui/store/mod.rs index 5771d96..0343ee4 100644 --- a/src/ui/store/mod.rs +++ b/src/ui/store/mod.rs @@ -1,50 +1,52 @@ pub(crate) mod room; pub(crate) mod space; -use std::cell::RefCell; use std::{collections::HashMap, rc::Rc}; use async_trait::async_trait; use dioxus::prelude::*; -use crate::domain::model::room::{Room as DomainRoom, RoomId}; -use crate::domain::model::space::{Space as DomainSpace, SpaceId}; +use crate::domain::model::room::RoomId; +use crate::domain::model::space::SpaceId; use crate::domain::model::store_interface::{ - AccountStoreProviderInterface, RoomStoreProviderInterface, SpaceStoreProviderInterface, + AccountStoreProviderInterface, RoomStoreConsumerInterface, RoomStoreProviderInterface, + SpaceStoreConsumerInterface, SpaceStoreProviderInterface, }; -use room::{Room, RoomProps}; -use space::{Space, SpaceProps}; +use room::Room; +use space::Space; #[modx::store] pub struct Store { - rooms: HashMap, - spaces: HashMap, + rooms: HashMap>, + spaces: HashMap>, } #[async_trait(?Send)] impl AccountStoreProviderInterface for GlobalSignal { - fn on_new_room(&self, domain_room: Rc) -> Rc { + fn on_new_room( + &self, + domain_room: Rc, + ) -> Rc { let room_id = domain_room.id(); - - let props = RoomProps::new(room_id.clone(), domain_room.name()); - let room = Room::new(props); + let room = Rc::new(Room::from_domain(Rc::clone(&domain_room))); let mut rooms = self.read().rooms; - rooms.write().insert(room_id.clone(), room); + rooms.write().insert(room_id.clone(), Rc::clone(&room)); - Rc::new(room.clone()) + room } - fn on_new_space(&self, domain_space: Rc) -> Rc { + fn on_new_space( + &self, + domain_space: Rc, + ) -> Rc { let space_id = domain_space.id(); - - let props = SpaceProps::new(space_id.clone(), domain_space.name()); - let space = Space::new(props); + let space = Rc::new(Space::from_domain(Rc::clone(&domain_space))); let mut spaces = self.read().spaces; - spaces.write().insert(space_id.clone(), space); + spaces.write().insert(space_id.clone(), Rc::clone(&space)); - Rc::new(RefCell::new(space.clone())) + space } } diff --git a/src/ui/store/room.rs b/src/ui/store/room.rs index 40bad22..a1c1396 100644 --- a/src/ui/store/room.rs +++ b/src/ui/store/room.rs @@ -1,21 +1,73 @@ +use std::cell::RefCell; +use std::rc::Rc; + use dioxus::prelude::*; +use crate::domain::model::room::Invitation; use crate::domain::model::{ - room::RoomId, room_member::RoomMember, store_interface::RoomStoreProviderInterface, + room::RoomId, + room_member::RoomMember, + store_interface::{RoomStoreConsumerInterface, RoomStoreProviderInterface}, }; -#[modx::props(id, name)] +#[modx::props(id, is_direct, name)] #[modx::store] -pub struct Room { +pub struct Store { id: RoomId, + + is_direct: Option, name: Option, + members: Vec, + invitations: Vec, + is_invited: bool, +} + +#[derive(Clone)] +pub struct Room { + store: RefCell, + domain: Rc, +} + +impl Room { + pub fn signal(&self) -> Store { + self.store.borrow().clone() + } + + pub fn from_domain(room: Rc) -> Self { + let props = StoreProps::new(room.id().clone(), room.is_direct(), room.name()); + + Self { + store: RefCell::new(Store::new(props)), + domain: room, + } + } } impl PartialEq for Room { fn eq(&self, other: &Self) -> bool { - self.id == other.id + self.store.borrow().id == other.store.borrow().id } } -impl RoomStoreProviderInterface for Room {} +impl RoomStoreProviderInterface for Room { + fn on_new_member(&self, member: RoomMember) { + let mut store = self.store.borrow_mut(); + store.members.write().push(member); + } + + fn on_new_name(&self, name: Option) { + let mut store = self.store.borrow_mut(); + store.name.set(name); + } + + fn on_invitation(&self, invitation: Invitation) { + let mut store = self.store.borrow_mut(); + + if !store.is_invited() && invitation.is_account_user() { + store.is_invited.set(true); + } + + store.invitations.write().push(invitation); + } +} diff --git a/src/ui/store/space.rs b/src/ui/store/space.rs index 8ebaeb8..4bb7897 100644 --- a/src/ui/store/space.rs +++ b/src/ui/store/space.rs @@ -1,24 +1,50 @@ use std::cell::RefCell; +use std::rc::Rc; use dioxus::prelude::*; -use crate::domain::model::{space::SpaceId, store_interface::SpaceStoreProviderInterface}; +use crate::domain::model::{ + space::SpaceId, + store_interface::{SpaceStoreConsumerInterface, SpaceStoreProviderInterface}, +}; #[modx::props(id, name)] #[modx::store] -pub struct Space { +pub struct Store { id: SpaceId, name: Option, } -impl PartialEq for Space { - fn eq(&self, other: &Self) -> bool { - self.id == other.id +#[derive(Clone)] +pub struct Space { + store: RefCell, + domain: Rc, +} + +impl Space { + pub fn signal(&self) -> Store { + self.store.borrow().clone() + } + + pub fn from_domain(space: Rc) -> Self { + let props = StoreProps::new(space.id().clone(), space.name()); + + Self { + store: RefCell::new(Store::new(props)), + domain: space, + } } } -impl SpaceStoreProviderInterface for RefCell { - fn set_name(&self, name: Option) { - self.borrow_mut().name.set(name); +impl PartialEq for Space { + fn eq(&self, other: &Self) -> bool { + self.store.borrow().id == other.store.borrow().id + } +} + +impl SpaceStoreProviderInterface for Space { + fn set_name(&self, name: Option) { + let mut store = self.store.borrow_mut(); + store.name.set(name); } } From cbe32c250ee876330222b95271e0f6f78a897403 Mon Sep 17 00:00:00 2001 From: Adrien Date: Thu, 16 May 2024 22:31:48 +0200 Subject: [PATCH 22/41] =?UTF-8?q?=F0=9F=9A=A7=20Add=20relations=20between?= =?UTF-8?q?=20store::Room=20and=20store::Area?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/room.rs | 5 ++++ src/domain/model/store_interface.rs | 12 ++++++---- src/ui/store/mod.rs | 10 ++++++++ src/ui/store/room.rs | 36 +++++++++++++++++++---------- src/ui/store/space.rs | 11 +++++---- 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index 53d757d..93d0da5 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -208,6 +208,7 @@ impl RoomMessagingConsumerInterface for Room { } } +#[async_trait(?Send)] impl RoomStoreConsumerInterface for Room { fn id(&self) -> &RoomId { &self.id @@ -220,4 +221,8 @@ impl RoomStoreConsumerInterface for Room { fn name(&self) -> Option { self.name.borrow().clone() } + + fn spaces(&self) -> &Vec { + &self.spaces + } } diff --git a/src/domain/model/store_interface.rs b/src/domain/model/store_interface.rs index 520f700..2077d05 100644 --- a/src/domain/model/store_interface.rs +++ b/src/domain/model/store_interface.rs @@ -1,6 +1,9 @@ use std::rc::Rc; +use async_trait::async_trait; + use super::{ + common::Avatar, room::{Invitation, RoomId}, room_member::RoomMember, space::SpaceId, @@ -20,24 +23,25 @@ pub trait AccountStoreProviderInterface { ) -> Rc; } -#[allow(dead_code)] +#[async_trait(?Send)] pub trait RoomStoreConsumerInterface { fn id(&self) -> &RoomId; - fn is_direct(&self) -> Option; fn name(&self) -> Option; + async fn avatar(&self) -> Option; + fn spaces(&self) -> &Vec; } pub trait RoomStoreProviderInterface { - fn on_new_member(&self, member: RoomMember); fn on_new_name(&self, name: Option); + fn on_new_avatar(&self, avatar: Option); + fn on_new_member(&self, member: RoomMember); fn on_invitation(&self, invitation: Invitation); } #[allow(dead_code)] pub trait SpaceStoreConsumerInterface { fn id(&self) -> &SpaceId; - fn name(&self) -> Option; } diff --git a/src/ui/store/mod.rs b/src/ui/store/mod.rs index 0343ee4..208ae74 100644 --- a/src/ui/store/mod.rs +++ b/src/ui/store/mod.rs @@ -5,6 +5,7 @@ use std::{collections::HashMap, rc::Rc}; use async_trait::async_trait; use dioxus::prelude::*; +use tracing::error; use crate::domain::model::room::RoomId; use crate::domain::model::space::SpaceId; @@ -34,6 +35,15 @@ impl AccountStoreProviderInterface for GlobalSignal { let mut rooms = self.read().rooms; rooms.write().insert(room_id.clone(), Rc::clone(&room)); + let spaces = self.read().spaces; + for space_id in domain_room.spaces() { + if let Some(space) = spaces.read().get(space_id) { + space.add_room(room_id.clone()); + } else { + error!("No {} space found", space_id); + } + } + room } diff --git a/src/ui/store/room.rs b/src/ui/store/room.rs index a1c1396..a5cb542 100644 --- a/src/ui/store/room.rs +++ b/src/ui/store/room.rs @@ -3,24 +3,28 @@ use std::rc::Rc; use dioxus::prelude::*; +use crate::domain::model::common::Avatar; use crate::domain::model::room::Invitation; +use crate::domain::model::space::SpaceId; use crate::domain::model::{ room::RoomId, room_member::RoomMember, store_interface::{RoomStoreConsumerInterface, RoomStoreProviderInterface}, }; -#[modx::props(id, is_direct, name)] +#[modx::props(id, is_direct, name, spaces)] #[modx::store] pub struct Store { id: RoomId, is_direct: Option, name: Option, - + avatar: Option, members: Vec, invitations: Vec, is_invited: bool, + + spaces: Vec, } #[derive(Clone)] @@ -35,32 +39,40 @@ impl Room { } pub fn from_domain(room: Rc) -> Self { - let props = StoreProps::new(room.id().clone(), room.is_direct(), room.name()); + let props = StoreProps::new( + room.id().clone(), + room.is_direct(), + room.name(), + room.spaces().clone(), + ); Self { store: RefCell::new(Store::new(props)), domain: room, } } -} -impl PartialEq for Room { - fn eq(&self, other: &Self) -> bool { - self.store.borrow().id == other.store.borrow().id + pub async fn get_avatar(&self) -> Option { + self.domain.avatar().await } } impl RoomStoreProviderInterface for Room { - fn on_new_member(&self, member: RoomMember) { - let mut store = self.store.borrow_mut(); - store.members.write().push(member); - } - fn on_new_name(&self, name: Option) { let mut store = self.store.borrow_mut(); store.name.set(name); } + fn on_new_avatar(&self, avatar: Option) { + let mut store = self.store.borrow_mut(); + store.avatar.set(avatar); + } + + fn on_new_member(&self, member: RoomMember) { + let mut store = self.store.borrow_mut(); + store.members.write().push(member); + } + fn on_invitation(&self, invitation: Invitation) { let mut store = self.store.borrow_mut(); diff --git a/src/ui/store/space.rs b/src/ui/store/space.rs index 4bb7897..c476bae 100644 --- a/src/ui/store/space.rs +++ b/src/ui/store/space.rs @@ -1,9 +1,10 @@ -use std::cell::RefCell; use std::rc::Rc; +use std::{cell::RefCell, collections::HashSet}; use dioxus::prelude::*; use crate::domain::model::{ + room::RoomId, space::SpaceId, store_interface::{SpaceStoreConsumerInterface, SpaceStoreProviderInterface}, }; @@ -13,6 +14,7 @@ use crate::domain::model::{ pub struct Store { id: SpaceId, name: Option, + room_ids: HashSet, } #[derive(Clone)] @@ -34,11 +36,10 @@ impl Space { domain: space, } } -} -impl PartialEq for Space { - fn eq(&self, other: &Self) -> bool { - self.store.borrow().id == other.store.borrow().id + pub fn add_room(&self, room_id: RoomId) { + let mut store = self.store.borrow_mut(); + store.room_ids.write().insert(room_id); } } From 0b898dce52f6f9650561f02b42789d0bef8fcbc4 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 17 May 2024 09:31:59 +0200 Subject: [PATCH 23/41] =?UTF-8?q?=E2=9C=A8=20Add=20Invitation=20value=20ob?= =?UTF-8?q?ject?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/messaging_interface.rs | 4 +- src/domain/model/room.rs | 70 ++++++++++++++++++++----- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/domain/model/messaging_interface.rs b/src/domain/model/messaging_interface.rs index 9a1fb60..7e70b51 100644 --- a/src/domain/model/messaging_interface.rs +++ b/src/domain/model/messaging_interface.rs @@ -5,8 +5,8 @@ use tokio::sync::broadcast::Receiver; use super::{ common::{Avatar, UserId}, - room::{Room, RoomId}, room_member::RoomMember, + room::{Invitation, Room, RoomId}, space::Space, }; use crate::infrastructure::messaging::matrix::account_event::AccountEvent; @@ -31,7 +31,7 @@ pub trait AccountMessagingProviderInterface { #[async_trait(?Send)] pub trait RoomMessagingConsumerInterface { - async fn on_invitation(&self) {} + async fn on_invitation(&self, _invitation: Invitation) {} async fn on_new_topic(&self, _topic: Option) {} async fn on_new_name(&self, _name: Option) {} diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index 93d0da5..23d100a 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -1,4 +1,9 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{ + cell::RefCell, + collections::HashMap, + fmt::{Debug, Formatter}, + rc::Rc, +}; use async_trait::async_trait; use futures::future::{join, join_all}; @@ -11,26 +16,58 @@ use super::{ messaging_interface::{RoomMessagingConsumerInterface, RoomMessagingProviderInterface}, room_member::RoomMember, space::SpaceId, - store_interface::RoomStoreProviderInterface, + store_interface::{RoomStoreConsumerInterface, RoomStoreProviderInterface}, }; use crate::infrastructure::services::mozaik_builder::create_mozaik; pub type RoomId = OwnedRoomId; +#[derive(PartialEq, Clone)] +pub struct Invitation { + invitee_id: UserId, + sender_id: UserId, + is_account_user: bool, +} + +impl Invitation { + pub fn new(invitee_id: UserId, sender_id: UserId, is_account_user: bool) -> Self { + Self { + invitee_id, + sender_id, + is_account_user, + } + } + + pub fn is_account_user(&self) -> bool { + self.is_account_user + } +} + +impl Debug for Invitation { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_tuple("Invitation") + .field(&self.invitee_id) + .field(&self.sender_id) + .field(&self.is_account_user) + .finish() + } +} + pub struct Room { id: RoomId, - #[allow(dead_code)] - spaces: Vec, - name: RefCell>, topic: Option, is_direct: Option, state: Option, avatar: RefCell>, + + invitations: RefCell>, members: RefCell>, + spaces: Vec, + messaging_provider: Option>, store: RefCell>>, } @@ -53,14 +90,17 @@ impl Room { Self { id, - spaces, name: RefCell::new(name), topic, is_direct, state, avatar: RefCell::new(None), + + invitations: RefCell::new(HashMap::new()), members: RefCell::new(HashMap::new()), + spaces, + messaging_provider: None, store: RefCell::new(None), } @@ -110,11 +150,17 @@ impl Room { self.state.map(|state| state == MatrixRoomState::Invited) } - #[allow(dead_code)] - fn add_member(&self, member: RoomMember) { - self.members + fn add_invitation(&self, invitation: Invitation) { + self.members.borrow_mut().remove(&invitation.invitee_id); + + self.invitations .borrow_mut() - .insert(member.id().clone(), member); + .insert(invitation.invitee_id.clone(), invitation.clone()); + + if let Some(store) = self.store.borrow().as_ref() { + store.on_invitation(invitation); + } + } } pub async fn get_avatar(&self) -> Option { @@ -194,8 +240,8 @@ impl Room { #[async_trait(?Send)] impl RoomMessagingConsumerInterface for Room { - async fn on_invitation(&self) { - trace!("Room::on_invitation"); + async fn on_invitation(&self, invitation: Invitation) { + self.add_invitation(invitation); } async fn on_membership(&self, member: RoomMember) { trace!("Room::on_membership({:?})", member); From fdae149c4ac34b1e5fac9122a0b91e7cc4c21139 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 17 May 2024 22:41:35 +0200 Subject: [PATCH 24/41] =?UTF-8?q?=F0=9F=9A=A7=20Add=20Avatar=20management?= =?UTF-8?q?=20and=20refresh=20the=20Matrix=20client=20part?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/messaging_interface.rs | 4 +- src/infrastructure/messaging/matrix/client.rs | 642 ++++++++++-------- .../messaging/matrix/requester.rs | 31 +- .../messaging/matrix/room_event.rs | 27 +- .../messaging/matrix/worker_tasks.rs | 6 +- 5 files changed, 399 insertions(+), 311 deletions(-) diff --git a/src/domain/model/messaging_interface.rs b/src/domain/model/messaging_interface.rs index 7e70b51..7f3ce4e 100644 --- a/src/domain/model/messaging_interface.rs +++ b/src/domain/model/messaging_interface.rs @@ -5,8 +5,8 @@ use tokio::sync::broadcast::Receiver; use super::{ common::{Avatar, UserId}, - room_member::RoomMember, room::{Invitation, Room, RoomId}, + room_member::{AvatarUrl, RoomMember}, space::Space, }; use crate::infrastructure::messaging::matrix::account_event::AccountEvent; @@ -35,6 +35,7 @@ pub trait RoomMessagingConsumerInterface { async fn on_new_topic(&self, _topic: Option) {} async fn on_new_name(&self, _name: Option) {} + async fn on_new_avatar(&self, _url: Option) {} #[allow(dead_code)] async fn on_membership(&self, _member: RoomMember) {} @@ -63,5 +64,6 @@ pub trait MemberMessagingProviderInterface { &self, room_id: &RoomId, user_id: &UserId, + avatar_url: &Option, ) -> anyhow::Result>; } diff --git a/src/infrastructure/messaging/matrix/client.rs b/src/infrastructure/messaging/matrix/client.rs index 295b909..cfba798 100644 --- a/src/infrastructure/messaging/matrix/client.rs +++ b/src/infrastructure/messaging/matrix/client.rs @@ -9,20 +9,22 @@ use dioxus::prelude::Task; use matrix_sdk::{ config::SyncSettings, event_handler::Ctx, - media::{MediaFormat, MediaThumbnailSize}, - room::{Room, RoomMember}, + media::{MediaFormat, MediaRequest, MediaThumbnailSize}, + room::{ParentSpace, Room, RoomMember}, ruma::{ api::client::media::get_content_thumbnail::v3::Method, events::{ room::{ - member::{ - OriginalSyncRoomMemberEvent, RoomMemberEventContent, StrippedRoomMemberEvent, - }, - topic::RoomTopicEventContent, + avatar::{RoomAvatarEventContent, StrippedRoomAvatarEvent}, + create::{RoomCreateEventContent, StrippedRoomCreateEvent}, + member::{MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent}, + name::{RoomNameEventContent, StrippedRoomNameEvent}, + topic::{RoomTopicEventContent, StrippedRoomTopicEvent}, + MediaSource, }, SyncStateEvent, }, - uint, OwnedRoomId, RoomId, UserId, + uint, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UserId, }, Client as MatrixClient, RoomMemberships, RoomState, }; @@ -68,21 +70,29 @@ impl Senders { room_senders.contains_key(room_id) } - fn send(&self, room_id: &RoomId, event: RoomEvent) -> Result> { + fn send(&self, room_id: &RoomId, event: RoomEvent) -> Result<(), SendError> { let room_senders = self.room_events_senders.lock().unwrap(); + if let Some(room_sender) = room_senders.get(room_id) { - room_sender.send(event) + if let Err(err) = room_sender.send(event) { + error!("Unable to send event to the {room_id} room: {err}"); + return Err(err); + } } else { - error!("No sender found for \"{}\" room id", room_id); - Ok(0) + warn!("No sender found for {room_id} room"); + // TODO: Return error } + Ok(()) } fn add_room(&self, room_id: &OwnedRoomId) -> Option { let mut senders = self.room_events_senders.lock().unwrap(); if !senders.contains_key(room_id) { let (room_sender, room_receiver) = broadcast::channel(32); + senders.insert(room_id.clone(), room_sender); + debug!("Create sender for {room_id} room"); + Some(RoomEventsReceiver::new(room_receiver)) } else { None @@ -109,45 +119,43 @@ impl Client { async fn create_space( senders: &Ctx, - room_id: OwnedRoomId, + room_id: &OwnedRoomId, room: Option<&Room>, ) -> anyhow::Result<(), SendError> { - let mut name = None; - let mut topic = None; - - if let Some(room) = room { - name = room.name(); - topic = room.topic(); - } - if let Some(receiver) = senders.add_room(&room_id) { + let mut name = None; + let mut topic = None; + if let Some(room) = room { + name = room.name(); + topic = room.topic(); + } + let (reply, mut response) = oneshot::(); - let event = AccountEvent::NewSpace(room_id.clone(), name, topic, receiver, reply); + let event = AccountEvent::NewSpace( + room_id.clone(), + name.clone(), + topic.clone(), + receiver, + reply, + ); if let Err(err) = senders.account_events_sender.send(event) { - error!( - "Unable to publish the new room with \"{}\" id: {}", - room_id, err - ); return Err(err); } // We're expecting a response indicating that the client is able to compute the next RoomEvent response.recv().await; - } else { + let events = vec![RoomEvent::NewTopic(topic), RoomEvent::NewName(name)]; for event in events { - if let Err(err) = senders.send(&room_id, event.clone()) { - error!( - "Unable to publish the {:?} event to the \"{}\" room: {}", - event, room_id, err - ); - // return Err(err); + if let Err(_err) = senders.send(&room_id, event.clone()) { + // TODO: Return an error } } } + Ok(()) } @@ -163,24 +171,25 @@ impl Client { let is_direct = match room.is_direct().await { Ok(is_direct) => Some(is_direct), Err(err) => { - error!("Unable to know if the room \"{room_id}\" is direct: {err}"); + error!("Unable to know if the {room_id} room is direct: {err}"); None } }; let mut parents = vec![]; - // TODO: Remove unwrap - let mut spaces = room.parent_spaces().await.unwrap(); - while let Some(parent) = spaces.next().await { - match parent { - Ok(parent) => match parent { - matrix_sdk::room::ParentSpace::Reciprocal(parent) => { - parents.push(parent.room_id().to_owned()); + + if let Ok(mut spaces) = room.parent_spaces().await { + while let Some(parent) = spaces.next().await { + match parent { + Ok(parent) => match parent { + ParentSpace::Reciprocal(parent) => { + parents.push(parent.room_id().to_owned()); + } + _ => todo!(), + }, + Err(err) => { + error!("{err}"); } - _ => todo!(), - }, - Err(err) => { - error!("{}", err); } } } @@ -197,11 +206,6 @@ impl Client { ); if let Err(err) = senders.account_events_sender.send(event) { - error!( - "Unable to publish the new room with \"{}\" id: {}", - room.room_id(), - err - ); return Err(err); } @@ -218,255 +222,283 @@ impl Client { let room_id = room.room_id().to_owned(); if room.is_space() { - Self::create_space(senders, room_id, Some(room)).await + Self::create_space(senders, &room_id, Some(room)).await } else { - let ret = Self::create_room(senders, room).await; - let mut parents = vec![]; - // TODO: Remove unwrap - let mut spaces = room.parent_spaces().await.unwrap(); - while let Some(parent) = spaces.next().await { - match parent { - Ok(parent) => match parent { - matrix_sdk::room::ParentSpace::Reciprocal(parent) => { - parents.push(parent.room_id().to_owned()); + + if let Ok(mut spaces) = room.parent_spaces().await { + while let Some(parent) = spaces.next().await { + match parent { + Ok(parent) => match parent { + ParentSpace::Reciprocal(parent) => { + parents.push(parent.room_id().to_owned()); + } + _ => { + warn!( + "Only ParentSpace::Reciprocal taken into account, skip {:?}", + parent + ); + } + }, + Err(err) => { + error!("{err}"); } - _ => { - warn!( - "Only ParentSpace::Reciprocal taken into account, skip {:?}", - parent - ); - } - }, - Err(err) => { - error!("{}", err); } } } - error!("parents={:?}", &parents); for parent in parents { // Create a minimal space to make the relation consistent... its content will be sync later. if !senders.contains(&parent) { - let _ = Self::create_space(senders, parent.clone(), None).await; + let _ = Self::create_space(senders, &parent, None).await; } let event = RoomEvent::NewChild(room_id.clone()); - if let Err(err) = senders.send(parent.as_ref(), event.clone()) { - error!( - "Unable to send the {:?} event to the \"{}\": {:?}", - event, parent, err - ); + if let Err(_err) = senders.send(&parent, event) { + // TODO: Return an error } } - ret + Self::create_room(senders, room).await } } - // async fn on_sync_typing_event(_ev: SyncTypingEvent, room: Room) { - // debug!("== on_sync_typing_event =="); - // let room_id = room.room_id().to_owned(); - // dbg!(room_id); - // } + async fn on_stripped_room_create_event( + _ev: StrippedRoomCreateEvent, + room: Room, + senders: Ctx, + ) { + let _ = Self::add_room(&senders, &room).await; + } - // async fn on_presence_event(_ev: PresenceEvent) { - // debug!("== on_presence_event =="); - // dbg!(_ev); - // } + // SyncStateEvent: A possibly-redacted state event without a room_id. + async fn on_sync_room_create_event( + _ev: SyncStateEvent, + room: Room, + senders: Ctx, + ) { + let _ = Self::add_room(&senders, &room).await; + } - // async fn on_sync_state_event(ev: SyncStateEvent, _room: Room) { - // error!("== on_sync_state_event =="); - // if let SyncStateEvent::Original(ev) = ev { - // dbg!(ev); - // } - // } + fn on_invite_room_member_event( + user_id: OwnedUserId, + inviter_id: OwnedUserId, + room: &Room, + matrix_client: &MatrixClient, + senders: &Ctx, + ) { + if let Some(client_user_id) = matrix_client.user_id() { + let is_account_user = user_id == client_user_id; + let room_id = room.room_id(); - // async fn on_original_sync_room_message_event( - // ev: OriginalSyncRoomMessageEvent, - // _room: Room, - // _senders: Ctx, - // ) { - // error!("== on_original_sync_room_message_event =="); - // error!("ev={:?}", ev.content); + debug!( + "{} (account user: {is_account_user}) invited by {} to join the {} room", + &user_id, &inviter_id, &room_id + ); + let event = RoomEvent::Invitation(user_id, inviter_id, is_account_user); + + if let Err(_err) = senders.send(room_id, event) { + // TODO: Return an error + } + } + } + + fn on_join_room_member_event( + user_id: OwnedUserId, + displayname: Option, + avatar_url: Option, + room: &Room, + matrix_client: &MatrixClient, + senders: &Ctx, + ) { + if let Some(client_user_id) = matrix_client.user_id() { + let is_account_user = user_id == client_user_id; + let room_id = room.room_id(); + + error!("{} has joined the {} room", &user_id, &room_id); + + let event = RoomEvent::Join(user_id, displayname, avatar_url, is_account_user); + + if let Err(_err) = senders.send(room_id, event) { + // TODO: Return an error + } + } + } + + // This function is called on each m.room.member event for an invited room preview (room not already joined). + // async fn on_stripped_room_member_event( async fn on_stripped_room_member_event( ev: StrippedRoomMemberEvent, matrix_client: MatrixClient, room: Room, senders: Ctx, ) { - error!("*** on_stripped_room_member_event ***"); - // error!("ev={:?}", ev); + match room.state() { + RoomState::Invited => { + let user_id = &ev.state_key; - if ev.state_key == matrix_client.user_id().unwrap() - && room.state() == RoomState::Invited - && Self::add_room(&senders, &room).await.is_ok() - { - let room_id = room.room_id(); - - let event = RoomEvent::Invitation(); - if let Err(err) = senders.send(room_id, event) { - error!( - "Unable to publish the room \"{}\" invitation: {}", - room.room_id(), - err - ); + match ev.content.membership { + MembershipState::Invite => Self::on_invite_room_member_event( + user_id.clone(), + ev.sender, + &room, + &matrix_client, + &senders, + ), + MembershipState::Join => Self::on_join_room_member_event( + ev.sender, + ev.content.displayname, + ev.content.avatar_url, + &room, + &matrix_client, + &senders, + ), + _ => { + error!("TODO: {:?}", ev); + } + } + } + _ => { + error!("TODO: {:?}", ev); } } } - async fn on_room_topic_event( + // SyncStateEvent: A possibly-redacted state event without a room_id. + // RoomMemberEventContent: The content of an m.room.member event. + async fn on_sync_room_member_event( + ev: SyncStateEvent, + matrix_client: MatrixClient, + room: Room, + senders: Ctx, + ) { + if let SyncStateEvent::Original(ev) = ev { + match ev.content.membership { + MembershipState::Invite => { + let invitee_id = ev.state_key; + + Self::on_invite_room_member_event( + invitee_id, + ev.sender, + &room, + &matrix_client, + &senders, + ) + // .await + } + MembershipState::Join => { + Self::on_join_room_member_event( + ev.sender, + ev.content.displayname, + ev.content.avatar_url, + &room, + &matrix_client, + &senders, + ) + // .await + } + _ => error!("TODO"), + } + } + } + + async fn on_room_avatar_event(room: &Room, senders: &Ctx) { + let room_id = room.room_id(); + let avatar = match room + .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + })) + .await + { + Ok(avatar) => avatar, + Err(err) => { + error!("Unable to fetch avatar for {}: {err}", &room_id); + None + } + }; + + let event = RoomEvent::NewAvatar(avatar); + + if let Err(_err) = senders.send(room_id, event) { + // TODO: Return an error + } + } + + async fn on_stripped_room_avatar_event( + _ev: StrippedRoomAvatarEvent, + room: Room, + senders: Ctx, + ) { + Self::on_room_avatar_event(&room, &senders).await; + } + + async fn on_sync_room_avatar_event( + ev: SyncStateEvent, + room: Room, + senders: Ctx, + ) { + if let SyncStateEvent::Original(_ev) = ev { + dioxus::prelude::spawn(async move { + Self::on_room_avatar_event(&room, &senders).await; + }); + } + } + + fn on_room_name_event(name: Option, room: &Room, senders: &Ctx) { + let event = RoomEvent::NewName(name); + + if let Err(_err) = senders.send(room.room_id(), event) { + // TODO: Return an error + } + } + + async fn on_stripped_room_name_event( + ev: StrippedRoomNameEvent, + room: Room, + senders: Ctx, + ) { + Self::on_room_name_event(ev.content.name, &room, &senders); + } + + async fn on_sync_room_name_event( + ev: SyncStateEvent, + room: Room, + senders: Ctx, + ) { + if let SyncStateEvent::Original(ev) = ev { + Self::on_room_name_event(Some(ev.content.name), &room, &senders); + } + } + + fn on_room_topic_event(topic: Option, room: &Room, senders: &Ctx) { + let event = RoomEvent::NewTopic(topic); + + if let Err(_err) = senders.send(room.room_id(), event) { + // TODO: Return an error + } + } + + async fn on_stripped_room_topic_event( + ev: StrippedRoomTopicEvent, + room: Room, + senders: Ctx, + ) { + Self::on_room_topic_event(ev.content.topic, &room, &senders); + } + + async fn on_sync_room_topic_event( ev: SyncStateEvent, room: Room, senders: Ctx, ) { - error!("*** on_room_topic_event ***"); - // error!("ev={:?}", ev); - if let SyncStateEvent::Original(ev) = ev { - let _ = Self::add_room(&senders, &room).await; - - let room_id = room.room_id(); - let event = RoomEvent::NewTopic(Some(ev.content.topic)); - if let Err(err) = senders.send(room_id, event) { - error!( - "Unable to publish the room \"{}\" topic: {}", - room.room_id(), - err - ); - } + Self::on_room_topic_event(Some(ev.content.topic), &room, &senders); } } - async fn on_room_member_event( - ev: SyncStateEvent, - room: Room, - senders: Ctx, - ) { - error!("*** on_room_member_event ***"); - // error!("ev={:?}", ev); - - if let SyncStateEvent::Original(_ev) = ev { - if Self::add_room(&senders, &room).await.is_ok() { - // let room_id = room.room_id(); - // // TODO: Client shall only manage Matrix object... not BG92 ones. - // let event = RoomEvent::Membership(RoomMember::new(ev.sender, room_id)); - // if let Some(result) = senders.send(room_id, event) { - // if let Err(err) = result { - // error!( - // "Unable to publish the room \"{}\" membership: {}", - // room.room_id(), - // err - // ); - // } - // } - } - } - } - - // async fn on_sync_message_like_room_message_event( - // ev: SyncMessageLikeEvent, - // _room: Room, - // _client: MatrixClient, - // ) { - // debug!("== on_sync_message_like_room_message_event =="); - // dbg!(ev); - // } - - // async fn on_sync_message_like_reaction_event( - // ev: SyncMessageLikeEvent, - // _room: Room, - // ) { - // debug!("== on_sync_message_like_reaction_event =="); - // dbg!(ev); - // } - - // async fn on_original_sync_room_redaction_event( - // ev: OriginalSyncRoomRedactionEvent, - // _room: Room, - // ) { - // debug!("== on_original_sync_room_redaction_event =="); - // dbg!(ev); - // } - - async fn on_original_sync_room_member_event( - _ev: OriginalSyncRoomMemberEvent, - _room: Room, - _client: MatrixClient, - ) { - // debug!("== on_original_sync_room_member_event =="); - // error!("room={:?}", room); - // 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: MatrixClient, - // ) { - // debug!("== on_original_sync_key_verif_start_event =="); - // dbg!(ev); - // } - - // async fn on_original_sync_key_verif_key_event( - // ev: OriginalSyncKeyVerificationKeyEvent, - // _client: MatrixClient, - // ) { - // debug!("== on_original_sync_key_verif_key_event =="); - // dbg!(ev); - // } - - // async fn on_original_sync_key_verif_done_event( - // ev: OriginalSyncKeyVerificationDoneEvent, - // _client: MatrixClient, - // ) { - // debug!("== on_original_sync_key_verif_done_event =="); - // dbg!(ev); - // } - - // async fn on_device_key_verif_req_event( - // ev: ToDeviceKeyVerificationRequestEvent, - // _client: MatrixClient, - // ) { - // debug!("== on_device_key_verif_req_event =="); - // dbg!(ev); - // } - - // async fn on_device_key_verif_start_event( - // ev: ToDeviceKeyVerificationStartEvent, - // _client: MatrixClient, - // ) { - // debug!("== on_device_key_verif_start_event =="); - // dbg!(ev); - // } - - // async fn on_device_key_verif_key_event( - // ev: ToDeviceKeyVerificationKeyEvent, - // _client: MatrixClient, - // ) { - // debug!("== on_device_key_verif_key_event =="); - // dbg!(ev); - // } - - // async fn on_device_key_verif_done_event( - // ev: ToDeviceKeyVerificationDoneEvent, - // _client: MatrixClient, - // ) { - // debug!("== on_device_key_verif_done_event =="); - // dbg!(ev); - // } - - // async fn on_room_event(ev: SomeEvent, _senders: Ctx) { - // debug!("== on_room_event({}) ==", ev.) - // } - pub async fn spawn(homeserver_url: String) -> (Requester, Receiver) { let matrix_client = Arc::new( MatrixClient::builder() @@ -494,28 +526,20 @@ impl Client { // TODO: Remove clone? client.add_event_handler_context(self.senders.clone()); + let _ = client.add_event_handler(Client::on_stripped_room_create_event); + let _ = client.add_event_handler(Client::on_sync_room_create_event); + let _ = client.add_event_handler(Client::on_stripped_room_member_event); - let _ = client.add_event_handler(Client::on_room_topic_event); - let _ = client.add_event_handler(Client::on_room_member_event); + let _ = client.add_event_handler(Client::on_sync_room_member_event); - // let _ = client.add_event_handler(Client::on_sync_typing_event); - // let _ = client.add_event_handler(Client::on_presence_event); - // let _ = client.add_event_handler(Client::on_sync_state_event); - // let _ = client.add_event_handler(Client::on_original_sync_room_message_event); + let _ = client.add_event_handler(Client::on_stripped_room_avatar_event); + let _ = client.add_event_handler(Client::on_sync_room_avatar_event); - // let _ = client.add_event_handler(Client::on_sync_message_like_room_message_event); - // let _ = client.add_event_handler(Client::on_sync_message_like_reaction_event); - // let _ = client.add_event_handler(Client::on_original_sync_room_redaction_event); + let _ = client.add_event_handler(Client::on_stripped_room_name_event); + let _ = client.add_event_handler(Client::on_sync_room_name_event); - let _ = client.add_event_handler(Client::on_original_sync_room_member_event); - - // let _ = client.add_event_handler(Client::on_original_sync_key_verif_start_event); - // let _ = client.add_event_handler(Client::on_original_sync_key_verif_key_event); - // let _ = client.add_event_handler(Client::on_original_sync_key_verif_done_event); - // let _ = client.add_event_handler(Client::on_device_key_verif_req_event); - // let _ = client.add_event_handler(Client::on_device_key_verif_start_event); - // let _ = client.add_event_handler(Client::on_device_key_verif_key_event); - // let _ = client.add_event_handler(Client::on_device_key_verif_done_event); + let _ = client.add_event_handler(Client::on_stripped_room_topic_event); + let _ = client.add_event_handler(Client::on_sync_room_topic_event); self.initialized = true; } @@ -625,27 +649,64 @@ impl Client { } } + // TODO: Share MediaRequest with other media requests + async fn get_thumbnail(&self, media_url: OwnedMxcUri) -> anyhow::Result> { + let client = self.client.as_ref().unwrap(); + let media = client.media(); + + let request = MediaRequest { + source: MediaSource::Plain(media_url), + format: MediaFormat::Thumbnail(MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + }), + }; + + let res = media.get_media_content(&request, true).await; + + Ok(res?) + } + async fn get_room_member_avatar( &self, room_id: &RoomId, user_id: &UserId, + avatar_url: &Option, ) -> anyhow::Result>> { let client = self.client.as_ref().unwrap(); if let Some(room) = client.get_room(room_id) { - if let Ok(Some(room_member)) = room.get_member(user_id).await { - let res = match room_member - .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { - method: Method::Scale, - width: uint!(256), - height: uint!(256), - })) - .await - { - Ok(avatar) => Ok(avatar), - Err(err) => Err(err.into()), - }; - return res; + // TODO: Check if we can get member before fetching the data and received an error... + + match room.get_member(user_id).await { + Ok(room_member) => match room_member { + Some(room_member) => { + let res = match room_member + .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + })) + .await + { + Ok(avatar) => Ok(avatar), + Err(err) => Err(err.into()), + }; + return res; + } + // TODO: Error msg + None => (), + }, + Err(err) => { + error!("Unable to get room member {user_id}: {err}"); + if let Some(avatar_url) = avatar_url { + let thumbnail = self.get_thumbnail(avatar_url.clone()).await; + return Ok(Some(thumbnail?)); + } else { + error!("No avatar url set for the {room_id} room"); + } + } } } Ok(None) @@ -691,9 +752,12 @@ impl Client { reply.send(self.get_room_members(&id).await).await; } - WorkerTask::GetRoomMemberAvatar(room_id, user_id, reply) => { + WorkerTask::GetRoomMemberAvatar(room_id, user_id, avatar_url, reply) => { reply - .send(self.get_room_member_avatar(&room_id, &user_id).await) + .send( + self.get_room_member_avatar(&room_id, &user_id, &avatar_url) + .await, + ) .await; } } diff --git a/src/infrastructure/messaging/matrix/requester.rs b/src/infrastructure/messaging/matrix/requester.rs index a1307cd..fc0e394 100644 --- a/src/infrastructure/messaging/matrix/requester.rs +++ b/src/infrastructure/messaging/matrix/requester.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, rc::Rc}; use async_trait::async_trait; -use futures::future::join_all; use tokio::{ select, sync::{broadcast::Receiver, mpsc::UnboundedSender}, @@ -23,8 +22,8 @@ use crate::{ RoomMessagingProviderInterface, SpaceMessagingConsumerInterface, SpaceMessagingProviderInterface, }, - room::{Room, RoomId}, - room_member::RoomMember, + room::{Invitation, Room, RoomId}, + room_member::{AvatarUrl, RoomMember}, space::Space, }, utils::oneshot, @@ -182,19 +181,29 @@ impl AccountMessagingProviderInterface for Requester { if let Ok(room_event) = room_event { if let Some(consumer) = room_events_consumers.get(&room_id) { match room_event { - RoomEvent::Invitation() => { - consumer.on_invitation().await; + RoomEvent::Invitation(user_id, sender_id, is_account_user) => { + let invitation = Invitation::new(user_id, sender_id, is_account_user); + consumer.on_invitation(invitation).await; + }, + RoomEvent::Join(user_id, user_name, avatar_url, is_account_user) => { + let member = RoomMember::new( + UserId::from(user_id), + user_name, + avatar_url, + room_id, + is_account_user, + client.clone()); + consumer.on_membership(member).await; }, - // RoomEvent::Membership(user_id, is_account_user) => { - // let member = RoomMember::new(UserId::from(user_id), room_id, is_account_user); - // consumer.on_membership(member).await; - // }, RoomEvent::NewTopic(topic) => { consumer.on_new_topic(topic).await; }, RoomEvent::NewName(name) => { consumer.on_new_name(name).await; }, + RoomEvent::NewAvatar(avatar) => { + consumer.on_new_avatar(avatar).await; + } _ => {} } } else { @@ -256,12 +265,14 @@ impl MemberMessagingProviderInterface for Requester { &self, room_id: &RoomId, user_id: &UserId, + avatar_url: &Option, ) -> anyhow::Result> { request_to_worker!( self, WorkerTask::GetRoomMemberAvatar, room_id.clone(), - user_id.clone() + user_id.clone(), + avatar_url.clone() ) } } diff --git a/src/infrastructure/messaging/matrix/room_event.rs b/src/infrastructure/messaging/matrix/room_event.rs index ca6690e..97b9df1 100644 --- a/src/infrastructure/messaging/matrix/room_event.rs +++ b/src/infrastructure/messaging/matrix/room_event.rs @@ -1,34 +1,43 @@ use std::fmt::{Debug, Formatter}; -use matrix_sdk::ruma::{OwnedRoomId, OwnedUserId}; +use matrix_sdk::ruma::{OwnedMxcUri, OwnedRoomId, OwnedUserId}; use tokio::sync::broadcast::Receiver; +use crate::domain::model::common::Avatar; + #[derive(Clone)] pub enum RoomEvent { - Invitation(), - - #[allow(dead_code)] - Membership(OwnedUserId, bool), + Invitation(OwnedUserId, OwnedUserId, bool), + Join(OwnedUserId, Option, Option, bool), NewTopic(Option), NewName(Option), + NewAvatar(Option), NewChild(OwnedRoomId), } impl Debug for RoomEvent { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { match self { - Self::Invitation() => f + Self::Invitation(invitee_id, sender_id, is_account_user) => f .debug_tuple("RoomEvent::Invitation") - .field(&format_args!("_")) + .field(invitee_id) + .field(sender_id) + .field(is_account_user) .finish(), - Self::Membership(user_id, is_account_user) => f - .debug_tuple("RoomEvent::Membership") + Self::Join(user_id, user_name, avatar_url, is_account_user) => f + .debug_tuple("RoomEvent::Join") .field(user_id) + .field(user_name) + .field(avatar_url) .field(is_account_user) .finish(), Self::NewTopic(topic) => f.debug_tuple("RoomEvent::NewTopic").field(topic).finish(), Self::NewName(name) => f.debug_tuple("RoomEvent::NewName").field(name).finish(), + Self::NewAvatar(avatar) => f + .debug_tuple("RoomEvent::NewAvatar") + .field(&format!("is_some: {}", &avatar.is_some())) + .finish(), Self::NewChild(room_id) => f .debug_tuple("SpaceEvent::NewChild") .field(room_id) diff --git a/src/infrastructure/messaging/matrix/worker_tasks.rs b/src/infrastructure/messaging/matrix/worker_tasks.rs index 0c0494a..380d15a 100644 --- a/src/infrastructure/messaging/matrix/worker_tasks.rs +++ b/src/infrastructure/messaging/matrix/worker_tasks.rs @@ -2,7 +2,7 @@ use std::fmt::{Debug, Formatter}; use matrix_sdk::{ room::RoomMember, - ruma::{OwnedRoomId, OwnedUserId}, + ruma::{OwnedMxcUri, OwnedRoomId, OwnedUserId}, }; use crate::utils::Sender; @@ -25,6 +25,7 @@ pub enum WorkerTask { GetRoomMemberAvatar( OwnedRoomId, OwnedUserId, + Option, Sender>>>, ), } @@ -64,10 +65,11 @@ impl Debug for WorkerTask { .field(id) .finish(), - WorkerTask::GetRoomMemberAvatar(room_id, user_id, _) => f + WorkerTask::GetRoomMemberAvatar(room_id, user_id, avatar_url, _) => f .debug_tuple("WorkerTask::GetRoomMemberAvatar") .field(room_id) .field(user_id) + .field(avatar_url) .finish(), } } From 54c7073b98a932a3bc52dd0944da3f2789bf4c4f Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 18 May 2024 09:52:40 +0200 Subject: [PATCH 25/41] =?UTF-8?q?=F0=9F=92=A1=20Add=20comments=20to=20keep?= =?UTF-8?q?=20in=20mind=20why=20we=20can't=20send=20Matrix=20sdk=20Room?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/messaging/matrix/client.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/infrastructure/messaging/matrix/client.rs b/src/infrastructure/messaging/matrix/client.rs index cfba798..0890cbd 100644 --- a/src/infrastructure/messaging/matrix/client.rs +++ b/src/infrastructure/messaging/matrix/client.rs @@ -132,6 +132,7 @@ impl Client { let (reply, mut response) = oneshot::(); + // We can't use Room instance here, because dyn PaginableRoom is not Sync let event = AccountEvent::NewSpace( room_id.clone(), name.clone(), @@ -194,6 +195,7 @@ impl Client { } } + // We can't use Room instance here, because dyn PaginableRoom is not Sync let event = AccountEvent::NewRoom( room_id.clone(), parents.clone(), @@ -327,7 +329,6 @@ impl Client { } // This function is called on each m.room.member event for an invited room preview (room not already joined). - // async fn on_stripped_room_member_event( async fn on_stripped_room_member_event( ev: StrippedRoomMemberEvent, matrix_client: MatrixClient, From df2d924c65bab99229148190ca3ad040a0077d87 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 18 May 2024 19:57:03 +0200 Subject: [PATCH 26/41] =?UTF-8?q?=E2=9E=96=20Cleanup=20dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 79 +++++++++++-------- src/base.rs | 2 +- .../services/random_svg_generators.rs | 2 +- src/ui/components/login.rs | 2 +- 4 files changed, 50 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d94127c..59d64bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,48 +4,63 @@ version = "0.1.0" edition = "2021" [features] -default = [] -desktop = ["dioxus/desktop"] -web = ["dioxus/web"] +default = ["web"] +desktop = ["dioxus/desktop", "tracing-subscriber/time"] +web = ["dioxus/web", "matrix-sdk/js"] [dependencies] -dioxus = "0.5.*" -dioxus-sdk = { version = "0.5.*", features = ["utils"] } -dioxus-free-icons = { version = "0.8.*", features = ["ionicons", "font-awesome-solid", "material-design-icons-navigation"] } -modx = "0.1.2" - -# matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", branch = "main", default-features = false, features = ["js", "rustls-tls"] } -matrix-sdk = { version = "0.7.*", default-features = false, features = ["js", "rustls-tls"] } - +# Errors anyhow = "1.0.75" -url = "2.5.0" -dirs = "5.0.1" -ctrlc-async = "3.2.2" thiserror = "1.0.50" -turf = "0.8.*" -tokio = { version = "1.34.0", default-features = false, features = ["rt", "sync"] } -log = "0.4.20" -futures-util = "0.3.29" -futures = "0.3.29" -rand = "0.8.5" -reqwest = "0.11.24" -validator = { version = "0.17.0", features = ["derive"] } -const_format = "0.2.32" -zxcvbn = "2.2.2" -async-std = "1.12.0" -tracing = "0.1.40" -tracing-web = "0.1.3" -tracing-subscriber = "0.3.18" -git-version = "0.3.9" -async-trait = "0.1.80" -tokio-stream = "0.1.15" -image = "0.25.1" +# Async +async-std = "1.12.0" +async-trait = "0.1.80" +futures = "0.3.29" +futures-util = "0.3.29" +tokio = { version = "1.34.0", default-features = false, features = ["rt", "sync"] } +tokio-stream = "0.1.15" + +# Utils +base64 = "0.22.0" +const_format = "0.2.32" +rand = "0.8.5" +validator = { version = "0.17.0", features = ["derive"] } +# Http client +reqwest = "0.11.24" +# Password strength estimation +zxcvbn = "2.2.2" +# Image processing/conversion +image = "0.25.1" +# Get the application version +git-version = "0.3.9" +# Conditional compilation +cfg-if = "1.0.0" + +# Logging/tracing +tracing = "0.1.40" +tracing-subscriber = "0.3.18" + +# SCSS -> CSS + usage in rust code +turf = "0.8.0" + +# Matrix +matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false, features = ["rustls-tls"] } + +# Dioxus +dioxus = { git = "https://github.com/ASR-ASU/dioxus.git", branch = "asr/add-scrolling-attributes" } +dioxus-free-icons = { git = "https://github.com/ASR-ASU/dioxus-free-icons.git", features = ["ionicons", "font-awesome-solid"] } +modx = "0.1.2" [target.'cfg(target_family = "wasm")'.dependencies] web-sys = "0.3.69" +tracing-web = "0.1.3" + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +time = "0.3.36" [build-dependencies] regex = "1.10.3" + [package.metadata.turf] minify = true diff --git a/src/base.rs b/src/base.rs index c906966..0cc57e1 100644 --- a/src/base.rs +++ b/src/base.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use dioxus::prelude::*; use futures_util::stream::StreamExt; -use log::{debug, error, warn}; +use tracing::{debug, error, warn}; use crate::domain::model::account::Account; use crate::domain::model::messaging_interface::AccountMessagingProviderInterface; diff --git a/src/infrastructure/services/random_svg_generators.rs b/src/infrastructure/services/random_svg_generators.rs index 0132010..dd4faf3 100644 --- a/src/infrastructure/services/random_svg_generators.rs +++ b/src/infrastructure/services/random_svg_generators.rs @@ -3,9 +3,9 @@ use std::future::Future; use std::sync::OnceLock; use std::{collections::HashMap, future::IntoFuture}; -use log::error; use rand::distributions::{Alphanumeric, DistString}; use reqwest::Result as RequestResult; +use tracing::error; #[cfg(feature = "desktop")] use tokio::fs::read_to_string; diff --git a/src/ui/components/login.rs b/src/ui/components/login.rs index 2f68653..012654c 100644 --- a/src/ui/components/login.rs +++ b/src/ui/components/login.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use const_format::formatcp; use dioxus::prelude::*; -use log::{debug, error, warn}; +use tracing::{debug, error, warn}; use validator::{Validate, ValidateArgs, ValidateEmail, ValidationError, ValidationErrors}; use zxcvbn::zxcvbn; From b5da0ee992db03e56678a232a412670976b98591 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 18 May 2024 22:01:58 +0200 Subject: [PATCH 27/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Use=20of=20cfg=5F?= =?UTF-8?q?if=20to=20manage=20how=20to=20logging=20according=20to=20the=20?= =?UTF-8?q?platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 73 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/src/main.rs b/src/main.rs index 592256b..67946ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,27 @@ #![allow(non_snake_case)] +#[macro_use] +extern crate cfg_if; + mod domain; mod infrastructure; mod ui; mod utils; use dioxus::prelude::*; - -#[cfg(feature = "desktop")] -use dioxus::desktop::Config; - -use tracing::debug; +use tracing::{debug, error}; use tracing_subscriber::prelude::*; -#[cfg(feature = "web")] -use tracing_web::MakeWebConsoleWriter; +cfg_if! { + if #[cfg(feature = "desktop")] { + use dioxus::desktop::Config; + use std::fs::File; + use time::format_description::well_known::Iso8601; + use tracing_subscriber::fmt::time::UtcTime; + } else if #[cfg(feature = "web")] { + use tracing_web::MakeWebConsoleWriter; + } +} use crate::base::{login, sync_rooms}; use crate::base::{APP_SETTINGS, ROOMS, SESSION}; @@ -99,27 +106,41 @@ fn app() -> Element { } fn main() { - #[cfg(feature = "desktop")] - { - let fmt_layer = tracing_subscriber::fmt::layer() - .with_filter(tracing::level_filters::LevelFilter::DEBUG); - tracing_subscriber::registry().with(fmt_layer).init(); + let mut builder = LaunchBuilder::new(); - let config = Config::new().with_menu(None); - let builder = LaunchBuilder::new().with_cfg(config); - builder.launch(app); + let mut layers = Vec::new(); + + cfg_if! { + if #[cfg(feature = "desktop")] { + let config = Config::new().with_menu(None);; + builder = builder.with_cfg(config); + + let log_file = File::create("/tmp/bg92.log").unwrap(); + let file_layer = tracing_subscriber::fmt::layer() + .with_writer(log_file) + .with_timer(UtcTime::new(Iso8601::DATE_TIME)) + .with_ansi(false) + .with_filter(tracing::level_filters::LevelFilter::WARN) + .boxed(); + layers.push(file_layer); + + let console_layer = tracing_subscriber::fmt::layer() + // .with_filter(tracing::level_filters::LevelFilter::DEBUG); + .with_filter(tracing::level_filters::LevelFilter::WARN) + .boxed(); + layers.push(console_layer); + + } else if #[cfg(feature = "web")] { + let console_layer = tracing_subscriber::fmt::layer() + .with_ansi(false) // Only partially supported across browsers + .without_time() // std::time is not available in browsers, see note below + .with_writer(MakeWebConsoleWriter::new()) // write events to the console + .with_filter(tracing::level_filters::LevelFilter::INFO); + layers.push(console_layer); + } } - #[cfg(feature = "web")] - { - let fmt_layer = tracing_subscriber::fmt::layer() - .with_ansi(false) // Only partially supported across browsers - .without_time() // std::time is not available in browsers, see note below - .with_writer(MakeWebConsoleWriter::new()) // write events to the console - .with_filter(tracing::level_filters::LevelFilter::INFO); - tracing_subscriber::registry().with(fmt_layer).init(); // Install these as subscribers to tracing events + tracing_subscriber::registry().with(layers).init(); - let builder = LaunchBuilder::new(); - builder.launch(app); - } + builder.launch(app); } From cd6506fb03d19bad8a1a74c119204c97ff143ba9 Mon Sep 17 00:00:00 2001 From: Adrien Date: Tue, 21 May 2024 12:22:04 +0200 Subject: [PATCH 28/41] =?UTF-8?q?=F0=9F=94=8A=20Trace=20events=20from=20Ma?= =?UTF-8?q?trix=20client=20callbacks=20to=20domain=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 3 +- src/domain/model/account.rs | 8 +- src/domain/model/room.rs | 41 +++- src/domain/model/space.rs | 13 +- .../messaging/matrix/account_event.rs | 34 ++- src/infrastructure/messaging/matrix/client.rs | 209 ++++++++++++------ .../messaging/matrix/requester.rs | 183 ++++++++++++--- .../messaging/matrix/room_event.rs | 28 ++- src/main.rs | 45 ++-- src/ui/store/room.rs | 2 +- src/ui/store/space.rs | 2 +- 11 files changed, 405 insertions(+), 163 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 59d64bb..ff0d443 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,8 @@ cfg-if = "1.0.0" # Logging/tracing tracing = "0.1.40" -tracing-subscriber = "0.3.18" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +tracing-forest = "0.1.6" # SCSS -> CSS + usage in rust code turf = "0.8.0" diff --git a/src/domain/model/account.rs b/src/domain/model/account.rs index 18e083a..e5aab71 100644 --- a/src/domain/model/account.rs +++ b/src/domain/model/account.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use async_trait::async_trait; -use tracing::error; +use tracing::{error, instrument, trace}; use super::{ common::PresenceState, @@ -95,7 +95,10 @@ impl Account { #[async_trait(?Send)] impl AccountMessagingConsumerInterface for Account { + #[instrument(name = "Account", skip_all)] async fn on_new_room(&self, room: Rc) -> Rc { + trace!("on_new_room"); + let room_id = room.id().clone(); self.by_id_rooms @@ -111,7 +114,10 @@ impl AccountMessagingConsumerInterface for Account { room } + #[instrument(name = "Account", skip_all)] async fn on_new_space(&self, space: Rc) -> Rc { + trace!("on_new_space"); + let space_id = space.id().clone(); self.by_id_spaces diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index 23d100a..eb98e4c 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -9,7 +9,7 @@ use async_trait::async_trait; use futures::future::{join, join_all}; use matrix_sdk::ruma::OwnedRoomId; use matrix_sdk::RoomState as MatrixRoomState; -use tracing::{debug, error, trace}; +use tracing::{debug, debug_span, error, instrument, trace}; use super::{ common::{Avatar, UserId}, @@ -18,7 +18,6 @@ use super::{ space::SpaceId, store_interface::{RoomStoreConsumerInterface, RoomStoreProviderInterface}, }; - use crate::infrastructure::services::mozaik_builder::create_mozaik; pub type RoomId = OwnedRoomId; @@ -150,6 +149,7 @@ impl Room { self.state.map(|state| state == MatrixRoomState::Invited) } + #[instrument(name = "Room", skip_all)] fn add_invitation(&self, invitation: Invitation) { self.members.borrow_mut().remove(&invitation.invitee_id); @@ -240,17 +240,44 @@ impl Room { #[async_trait(?Send)] impl RoomMessagingConsumerInterface for Room { + #[instrument(name = "Room", skip_all)] async fn on_invitation(&self, invitation: Invitation) { + trace!("on_invitation"); + let sender_id = invitation.sender_id.clone(); + self.add_invitation(invitation); + + if self.is_direct.unwrap_or(false) { + debug!("1to1 conversation, using the {} avatar", &sender_id); + if let Ok(avatar) = self.gen_room_avatar_with_members().await { + debug!("Avatar successfully generated"); + self.avatar.borrow_mut().clone_from(&avatar); + if let Some(store) = self.store.borrow().as_ref() { + store.on_new_avatar(avatar); + } + } + } } + + #[instrument(name = "Room", skip_all)] async fn on_membership(&self, member: RoomMember) { - trace!("Room::on_membership({:?})", member); + trace!("on_membership"); + self.add_member(member); } - async fn on_new_topic(&self, topic: Option) { - trace!("Room::on_new_topic({:?})", topic); + + #[instrument(name = "Room", skip_all)] + async fn on_new_topic(&self, _topic: Option) { + trace!("on_new_topic"); } - async fn on_new_name(&self, name: Option) { - trace!("Room::on_new_name({:?})", name); + + #[instrument(name = "Room", skip_all)] + async fn on_new_name(&self, _name: Option) { + trace!("on_new_name"); + } + + #[instrument(name = "Room", skip_all)] + async fn on_new_avatar(&self, avatar: Option) { + trace!("on_new_avatar"); } } diff --git a/src/domain/model/space.rs b/src/domain/model/space.rs index b269c61..2037a53 100644 --- a/src/domain/model/space.rs +++ b/src/domain/model/space.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, collections::HashSet, rc::Rc}; use async_trait::async_trait; use matrix_sdk::ruma::OwnedRoomId; -use tracing::error; +use tracing::{instrument, trace}; use super::{ common::Avatar, @@ -72,16 +72,21 @@ impl Space { #[async_trait(?Send)] impl SpaceMessagingConsumerInterface for Space { + #[instrument(name = "Space", skip_all)] async fn on_child(&self, room_id: RoomId) { - error!("Space::on_child({room_id})"); + trace!("on_child"); self.children.borrow_mut().insert(room_id); } + + #[instrument(name = "Space", skip_all)] async fn on_new_topic(&self, topic: Option) { - error!("Space::on_new_topic({:?})", topic); + trace!("on_new_topic"); *self.topic.borrow_mut() = topic; } + + #[instrument(name = "Space", skip_all)] async fn on_new_name(&self, name: Option) { - error!("Space::on_new_name({:?})", name); + trace!("on_new_name"); self.name.borrow_mut().clone_from(&name); if let Some(store) = self.store.borrow().as_ref() { diff --git a/src/infrastructure/messaging/matrix/account_event.rs b/src/infrastructure/messaging/matrix/account_event.rs index fecddf4..4a565e2 100644 --- a/src/infrastructure/messaging/matrix/account_event.rs +++ b/src/infrastructure/messaging/matrix/account_event.rs @@ -1,6 +1,7 @@ use std::fmt::{Debug, Formatter}; use matrix_sdk::{ruma::OwnedRoomId, RoomState}; +use tracing::Span; use super::room_event::RoomEventsReceiver; use crate::{domain::model::space::SpaceId, utils::Sender}; @@ -16,6 +17,7 @@ pub enum AccountEvent { RoomState, RoomEventsReceiver, Sender, + Span, ), NewSpace( @@ -24,23 +26,33 @@ pub enum AccountEvent { Option, RoomEventsReceiver, Sender, + Span, ), } impl Debug for AccountEvent { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::NewRoom(id, spaces, name, topic, is_direct, state, _events_receiver, _sender) => { - f.debug_tuple("AccountEvent::NewRoom") - .field(id) - .field(spaces) - .field(name) - .field(topic) - .field(is_direct) - .field(state) - .finish() - } - Self::NewSpace(id, name, topic, _events_receiver, _sender) => f + Self::NewRoom( + id, + spaces, + name, + topic, + is_direct, + state, + _events_receiver, + _sender, + _span, + ) => f + .debug_tuple("AccountEvent::NewRoom") + .field(id) + .field(spaces) + .field(name) + .field(topic) + .field(is_direct) + .field(state) + .finish(), + Self::NewSpace(id, name, topic, _events_receiver, _sender, _span) => f .debug_tuple("AccountEvent::NewSpace") .field(id) .field(name) diff --git a/src/infrastructure/messaging/matrix/client.rs b/src/infrastructure/messaging/matrix/client.rs index 0890cbd..7209745 100644 --- a/src/infrastructure/messaging/matrix/client.rs +++ b/src/infrastructure/messaging/matrix/client.rs @@ -28,13 +28,12 @@ use matrix_sdk::{ }, Client as MatrixClient, RoomMemberships, RoomState, }; - use tokio::sync::{ broadcast, broadcast::{error::SendError, Receiver, Sender}, mpsc::{unbounded_channel, UnboundedReceiver}, }; -use tracing::{debug, error, warn}; +use tracing::{debug, debug_span, error, instrument, warn, Instrument, Span}; use super::{ account_event::AccountEvent, @@ -75,7 +74,7 @@ impl Senders { if let Some(room_sender) = room_senders.get(room_id) { if let Err(err) = room_sender.send(event) { - error!("Unable to send event to the {room_id} room: {err}"); + warn!("Unable to send event to the {room_id} room: {err}"); return Err(err); } } else { @@ -117,12 +116,15 @@ impl Client { } } + #[instrument(skip_all)] async fn create_space( senders: &Ctx, room_id: &OwnedRoomId, room: Option<&Room>, ) -> anyhow::Result<(), SendError> { - if let Some(receiver) = senders.add_room(&room_id) { + if let Some(receiver) = senders.add_room(room_id) { + let current_span = Span::current(); + let mut name = None; let mut topic = None; if let Some(room) = room { @@ -139,19 +141,21 @@ impl Client { topic.clone(), receiver, reply, + current_span.clone(), ); - if let Err(err) = senders.account_events_sender.send(event) { - return Err(err); - } + senders.account_events_sender.send(event)?; // We're expecting a response indicating that the client is able to compute the next RoomEvent response.recv().await; - let events = vec![RoomEvent::NewTopic(topic), RoomEvent::NewName(name)]; + let events = vec![ + RoomEvent::NewTopic(topic, current_span.clone()), + RoomEvent::NewName(name, current_span), + ]; for event in events { - if let Err(_err) = senders.send(&room_id, event.clone()) { + if let Err(_err) = senders.send(room_id, event.clone()) { // TODO: Return an error } } @@ -160,6 +164,7 @@ impl Client { Ok(()) } + #[instrument(skip_all)] async fn create_room( senders: &Ctx, room: &Room, @@ -172,7 +177,7 @@ impl Client { let is_direct = match room.is_direct().await { Ok(is_direct) => Some(is_direct), Err(err) => { - error!("Unable to know if the {room_id} room is direct: {err}"); + warn!("Unable to know if the {room_id} room is direct: {err}"); None } }; @@ -205,11 +210,10 @@ impl Client { room.state(), receiver, reply, + Span::current(), ); - if let Err(err) = senders.account_events_sender.send(event) { - return Err(err); - } + senders.account_events_sender.send(event)?; // We're expecting a response indicating that the client is able to compute the next RoomEvent response.recv().await; @@ -217,6 +221,7 @@ impl Client { Ok(()) } + #[instrument(skip_all)] async fn add_room( senders: &Ctx, room: &Room, @@ -255,7 +260,7 @@ impl Client { let _ = Self::create_space(senders, &parent, None).await; } - let event = RoomEvent::NewChild(room_id.clone()); + let event = RoomEvent::NewChild(room_id.clone(), Span::current()); if let Err(_err) = senders.send(&parent, event) { // TODO: Return an error } @@ -270,7 +275,9 @@ impl Client { room: Room, senders: Ctx, ) { - let _ = Self::add_room(&senders, &room).await; + let span = debug_span!("Matrix::NewRoom", r = ?room.room_id()); + + let _ = Self::add_room(&senders, &room).instrument(span).await; } // SyncStateEvent: A possibly-redacted state event without a room_id. @@ -279,9 +286,12 @@ impl Client { room: Room, senders: Ctx, ) { - let _ = Self::add_room(&senders, &room).await; + let span = debug_span!("Matrix::NewRoom", r = ?room.room_id()); + + let _ = Self::add_room(&senders, &room).instrument(span).await; } + #[instrument(skip_all)] fn on_invite_room_member_event( user_id: OwnedUserId, inviter_id: OwnedUserId, @@ -290,15 +300,16 @@ impl Client { senders: &Ctx, ) { if let Some(client_user_id) = matrix_client.user_id() { - let is_account_user = user_id == client_user_id; let room_id = room.room_id(); + let is_account_user = user_id == client_user_id; debug!( "{} (account user: {is_account_user}) invited by {} to join the {} room", &user_id, &inviter_id, &room_id ); - let event = RoomEvent::Invitation(user_id, inviter_id, is_account_user); + let event = + RoomEvent::Invitation(user_id, inviter_id, is_account_user, Span::current()); if let Err(_err) = senders.send(room_id, event) { // TODO: Return an error @@ -306,6 +317,7 @@ impl Client { } } + #[instrument(skip_all)] fn on_join_room_member_event( user_id: OwnedUserId, displayname: Option, @@ -318,9 +330,15 @@ impl Client { let is_account_user = user_id == client_user_id; let room_id = room.room_id(); - error!("{} has joined the {} room", &user_id, &room_id); + debug!("{} has joined the {} room", &user_id, &room_id); - let event = RoomEvent::Join(user_id, displayname, avatar_url, is_account_user); + let event = RoomEvent::Join( + user_id, + displayname, + avatar_url, + is_account_user, + Span::current(), + ); if let Err(_err) = senders.send(room_id, event) { // TODO: Return an error @@ -340,21 +358,35 @@ impl Client { let user_id = &ev.state_key; match ev.content.membership { - MembershipState::Invite => Self::on_invite_room_member_event( - user_id.clone(), - ev.sender, - &room, - &matrix_client, - &senders, - ), - MembershipState::Join => Self::on_join_room_member_event( - ev.sender, - ev.content.displayname, - ev.content.avatar_url, - &room, - &matrix_client, - &senders, - ), + MembershipState::Invite => { + let span = debug_span!("Matrix::RoomInvitation", r = ?room.room_id()); + + span.in_scope(|| { + Self::on_invite_room_member_event( + user_id.clone(), + ev.sender, + &room, + &matrix_client, + &senders, + ) + }); + } + MembershipState::Join => { + let span = + debug_span!("Matrix::RoomJoin", r = ?room.room_id(), u = ?user_id) + .entered(); + + span.in_scope(|| { + Self::on_join_room_member_event( + ev.sender, + ev.content.displayname, + ev.content.avatar_url, + &room, + &matrix_client, + &senders, + ) + }); + } _ => { error!("TODO: {:?}", ev); } @@ -377,33 +409,42 @@ impl Client { if let SyncStateEvent::Original(ev) = ev { match ev.content.membership { MembershipState::Invite => { - let invitee_id = ev.state_key; + let span = debug_span!("Matrix::RoomInvitation", r = ?room.room_id()); - Self::on_invite_room_member_event( - invitee_id, - ev.sender, - &room, - &matrix_client, - &senders, - ) - // .await + span.in_scope(|| { + let invitee_id = ev.state_key; + + Self::on_invite_room_member_event( + invitee_id, + ev.sender, + &room, + &matrix_client, + &senders, + ) + }); } MembershipState::Join => { - Self::on_join_room_member_event( - ev.sender, - ev.content.displayname, - ev.content.avatar_url, - &room, - &matrix_client, - &senders, - ) - // .await + let user_id = ev.sender; + let span = debug_span!("Matrix::RoomJoin", r = ?room.room_id(), u = ?user_id) + .entered(); + + span.in_scope(|| { + Self::on_join_room_member_event( + user_id, + ev.content.displayname, + ev.content.avatar_url, + &room, + &matrix_client, + &senders, + ) + }); } _ => error!("TODO"), } } } + #[instrument(skip_all)] async fn on_room_avatar_event(room: &Room, senders: &Ctx) { let room_id = room.room_id(); let avatar = match room @@ -416,12 +457,12 @@ impl Client { { Ok(avatar) => avatar, Err(err) => { - error!("Unable to fetch avatar for {}: {err}", &room_id); + warn!("Unable to fetch avatar for {}: {err}", &room_id); None } }; - let event = RoomEvent::NewAvatar(avatar); + let event = RoomEvent::NewAvatar(avatar, Span::current()); if let Err(_err) = senders.send(room_id, event) { // TODO: Return an error @@ -433,7 +474,11 @@ impl Client { room: Room, senders: Ctx, ) { - Self::on_room_avatar_event(&room, &senders).await; + let span = debug_span!("Matrix::RoomAvatar", r = ?room.room_id()); + + Self::on_room_avatar_event(&room, &senders) + .instrument(span) + .await; } async fn on_sync_room_avatar_event( @@ -443,13 +488,18 @@ impl Client { ) { if let SyncStateEvent::Original(_ev) = ev { dioxus::prelude::spawn(async move { - Self::on_room_avatar_event(&room, &senders).await; + let span = debug_span!("Matrix::RoomAvatar", r = ?room.room_id()); + + Self::on_room_avatar_event(&room, &senders) + .instrument(span) + .await; }); } } + #[instrument(skip_all)] fn on_room_name_event(name: Option, room: &Room, senders: &Ctx) { - let event = RoomEvent::NewName(name); + let event = RoomEvent::NewName(name, Span::current()); if let Err(_err) = senders.send(room.room_id(), event) { // TODO: Return an error @@ -461,7 +511,11 @@ impl Client { room: Room, senders: Ctx, ) { - Self::on_room_name_event(ev.content.name, &room, &senders); + let span = debug_span!("Matrix::RoomName", r = ?room.room_id()); + + span.in_scope(|| { + Self::on_room_name_event(ev.content.name, &room, &senders); + }); } async fn on_sync_room_name_event( @@ -470,12 +524,17 @@ impl Client { senders: Ctx, ) { if let SyncStateEvent::Original(ev) = ev { - Self::on_room_name_event(Some(ev.content.name), &room, &senders); + let span = debug_span!("Matrix::RoomName", r = ?room.room_id()); + + span.in_scope(|| { + Self::on_room_name_event(Some(ev.content.name), &room, &senders); + }); } } + #[instrument(skip_all)] fn on_room_topic_event(topic: Option, room: &Room, senders: &Ctx) { - let event = RoomEvent::NewTopic(topic); + let event = RoomEvent::NewTopic(topic, Span::current()); if let Err(_err) = senders.send(room.room_id(), event) { // TODO: Return an error @@ -487,7 +546,11 @@ impl Client { room: Room, senders: Ctx, ) { - Self::on_room_topic_event(ev.content.topic, &room, &senders); + let span = debug_span!("Matrix::RoomTopic", r = ?room.room_id()); + + span.in_scope(|| { + Self::on_room_topic_event(ev.content.topic, &room, &senders); + }); } async fn on_sync_room_topic_event( @@ -496,7 +559,11 @@ impl Client { senders: Ctx, ) { if let SyncStateEvent::Original(ev) = ev { - Self::on_room_topic_event(Some(ev.content.topic), &room, &senders); + let span = debug_span!("Matrix::RoomTopic", r = ?room.room_id()); + + span.in_scope(|| { + Self::on_room_topic_event(Some(ev.content.topic), &room, &senders); + }); } } @@ -629,7 +696,7 @@ impl Client { Err(err) => Err(err.into()), } } else { - error!("No room found with the \"{}\" id", room_id.as_str()); + warn!("No room found with the \"{}\" id", room_id.as_str()); // TODO: Return an error if the room has not been found Ok(None) } @@ -644,7 +711,7 @@ impl Client { Err(err) => Err(err.into()), } } else { - error!("No room found with the \"{}\" id", room_id.as_str()); + warn!("No room found with the \"{}\" id", room_id.as_str()); // TODO: Return an error if the room has not been found Ok(vec![]) } @@ -681,8 +748,8 @@ impl Client { // TODO: Check if we can get member before fetching the data and received an error... match room.get_member(user_id).await { - Ok(room_member) => match room_member { - Some(room_member) => { + Ok(room_member) => { + if let Some(room_member) = room_member { let res = match room_member .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { method: Method::Scale, @@ -696,16 +763,14 @@ impl Client { }; return res; } - // TODO: Error msg - None => (), - }, + } Err(err) => { - error!("Unable to get room member {user_id}: {err}"); + warn!("Unable to get room member {user_id}: {err}"); if let Some(avatar_url) = avatar_url { let thumbnail = self.get_thumbnail(avatar_url.clone()).await; return Ok(Some(thumbnail?)); } else { - error!("No avatar url set for the {room_id} room"); + debug!("No avatar url set for the {room_id} room"); } } } diff --git a/src/infrastructure/messaging/matrix/requester.rs b/src/infrastructure/messaging/matrix/requester.rs index fc0e394..20e80c5 100644 --- a/src/infrastructure/messaging/matrix/requester.rs +++ b/src/infrastructure/messaging/matrix/requester.rs @@ -1,12 +1,13 @@ use std::{collections::HashMap, rc::Rc}; use async_trait::async_trait; +use matrix_sdk::ruma::{OwnedMxcUri, OwnedRoomId, OwnedUserId}; use tokio::{ select, sync::{broadcast::Receiver, mpsc::UnboundedSender}, }; use tokio_stream::{wrappers::BroadcastStream, StreamExt, StreamMap}; -use tracing::error; +use tracing::{error, instrument, Instrument}; use super::{ account_event::AccountEvent, @@ -96,6 +97,96 @@ impl Requester { pub async fn login(&self, style: LoginStyle) -> anyhow::Result<()> { request_to_worker!(self, WorkerTask::Login, style) } + + #[instrument(skip_all)] + async fn on_room_invitation( + consumer: &Rc, + user_id: OwnedUserId, + sender_id: OwnedUserId, + is_account_user: bool, + ) { + let invitation = Invitation::new(user_id, sender_id, is_account_user); + consumer.on_invitation(invitation).await; + } + + #[instrument(skip_all)] + async fn on_room_join( + consumer: &Rc, + room_id: OwnedRoomId, + user_id: OwnedUserId, + user_name: Option, + avatar_url: Option, + is_account_user: bool, + messaging_provider: Rc, + ) { + let member = RoomMember::new( + UserId::from(user_id), + user_name, + avatar_url, + room_id, + is_account_user, + messaging_provider, + ); + consumer.on_membership(member).await; + } + + #[instrument(skip_all)] + async fn on_room_new_topic( + consumer: &Rc, + topic: Option, + ) { + consumer.on_new_topic(topic).await; + } + + #[instrument(skip_all)] + async fn on_room_new_name( + consumer: &Rc, + name: Option, + ) { + consumer.on_new_name(name).await; + } + + #[instrument(skip_all)] + async fn on_room_new_avatar( + consumer: &Rc, + avatar: Option, + ) { + consumer.on_new_avatar(avatar).await; + } + + #[instrument(skip_all)] + async fn on_space_new_child( + consumer: &Rc, + child_id: RoomId, + ) { + // TODO: Make name consistent + consumer.on_child(child_id).await; + } + + #[instrument(skip_all)] + async fn on_space_new_topic( + consumer: &Rc, + topic: Option, + ) { + consumer.on_new_topic(topic).await; + } + + #[instrument(skip_all)] + async fn on_space_new_name( + consumer: &Rc, + name: Option, + ) { + consumer.on_new_name(name).await; + } + + // #[instrument(name="SpaceAvatar", skip_all, fields(s = %space_id, a = avatar.is_some()))] + // async fn on_space_new_avatar( + // consumer: &Rc, + // space_id: OwnedRoomId, + // avatar: Option, + // ) { + // consumer.on_new_avatar(avatar).await; + // } } #[async_trait(?Send)] @@ -140,7 +231,17 @@ impl AccountMessagingProviderInterface for Requester { res = account_events_receiver.recv() => { if let Ok(account_event) = res { match account_event { - AccountEvent::NewRoom(id, spaces, name, topic, is_direct, state, receiver, new_room_tx) => { + AccountEvent::NewRoom( + id, + spaces, + name, + topic, + is_direct, + state, + receiver, + new_room_tx, + span + ) => { let mut room = Room::new(id, spaces, name, topic, is_direct, Some(state)); let room_id = room.id().clone(); @@ -151,13 +252,15 @@ impl AccountMessagingProviderInterface for Requester { let stream = BroadcastStream::new(receiver.into()); rooms_events_streams.insert(room_id.clone(), stream); - let room_events_consumer = account_events_consumer.on_new_room(room).await; + let room_events_consumer = account_events_consumer.on_new_room(room) + .instrument(span) + .await; room_events_consumers.insert(room_id, room_events_consumer); // We're now ready to recv and compute RoomEvent. new_room_tx.send(true).await; }, - AccountEvent::NewSpace(id, name, topic, receiver, new_space_tx) => { + AccountEvent::NewSpace(id, name, topic, receiver, new_space_tx, span) => { let mut space = Space::new(id, name, topic); let space_id = space.id().clone(); @@ -168,12 +271,14 @@ impl AccountMessagingProviderInterface for Requester { let stream = BroadcastStream::new(receiver.into()); spaces_events_streams.insert(space_id.clone(), stream); - let space_events_consumer = account_events_consumer.on_new_space(space).await; + let space_events_consumer = account_events_consumer.on_new_space(space) + .instrument(span) + .await; space_events_consumers.insert(space_id, space_events_consumer); // We're now ready to recv and compute SpaceEvent. new_space_tx.send(true).await; - } + }, }; } }, @@ -181,33 +286,43 @@ impl AccountMessagingProviderInterface for Requester { if let Ok(room_event) = room_event { if let Some(consumer) = room_events_consumers.get(&room_id) { match room_event { - RoomEvent::Invitation(user_id, sender_id, is_account_user) => { - let invitation = Invitation::new(user_id, sender_id, is_account_user); - consumer.on_invitation(invitation).await; + RoomEvent::Invitation(user_id, sender_id, is_account_user, span) => { + Self::on_room_invitation(consumer, user_id, sender_id, is_account_user) + .instrument(span) + .await; }, - RoomEvent::Join(user_id, user_name, avatar_url, is_account_user) => { - let member = RoomMember::new( - UserId::from(user_id), + RoomEvent::Join(user_id, user_name, avatar_url, is_account_user, span) => { + Self::on_room_join( + consumer, + room_id, + user_id, user_name, avatar_url, - room_id, is_account_user, - client.clone()); - consumer.on_membership(member).await; + client.clone()) + .instrument(span) + .await; }, - RoomEvent::NewTopic(topic) => { - consumer.on_new_topic(topic).await; + RoomEvent::NewTopic(topic, span) => { + Self::on_room_new_topic(consumer, topic) + .instrument(span) + .await; }, - RoomEvent::NewName(name) => { - consumer.on_new_name(name).await; + RoomEvent::NewName(name, span) => { + Self::on_room_new_name(consumer, name) + .instrument(span) + .await; }, - RoomEvent::NewAvatar(avatar) => { - consumer.on_new_avatar(avatar).await; + RoomEvent::NewAvatar(avatar, span) => { + Self::on_room_new_avatar(consumer, avatar) + .instrument(span) + .await; } - _ => {} + // RoomEvent::NewAvatar(avatar) => Self::on_room_new_avatar(consumer, avatar).await, + _ => error!("TODO: {:?}", &room_event), } } else { - error!("No consumer found for \"{}\" room", room_id); + error!("No consumer found for {} room", &room_id); } } }, @@ -215,19 +330,25 @@ impl AccountMessagingProviderInterface for Requester { if let Ok(room_event) = room_event { if let Some(consumer) = space_events_consumers.get(&space_id) { match room_event { - RoomEvent::NewTopic(topic) => { - consumer.on_new_topic(topic).await; + RoomEvent::NewTopic(topic, span) => { + Self::on_space_new_topic(consumer, topic) + .instrument(span) + .await; }, - RoomEvent::NewName(name) => { - consumer.on_new_name(name).await; + RoomEvent::NewName(name, span) => { + Self::on_space_new_name(consumer, name) + .instrument(span) + .await; }, - RoomEvent::NewChild(child_id) => { - consumer.on_child(child_id).await; + RoomEvent::NewChild(child_id, span) => { + Self::on_space_new_child(consumer, child_id) + .instrument(span) + .await; }, - _ => {} + _ => error!("TODO: {:?}", &room_event), } } else { - error!("No consumer found for \"{}\" space", space_id); + error!("No consumer found for {} space", &space_id); } } } diff --git a/src/infrastructure/messaging/matrix/room_event.rs b/src/infrastructure/messaging/matrix/room_event.rs index 97b9df1..e3edfeb 100644 --- a/src/infrastructure/messaging/matrix/room_event.rs +++ b/src/infrastructure/messaging/matrix/room_event.rs @@ -2,43 +2,47 @@ use std::fmt::{Debug, Formatter}; use matrix_sdk::ruma::{OwnedMxcUri, OwnedRoomId, OwnedUserId}; use tokio::sync::broadcast::Receiver; +use tracing::Span; use crate::domain::model::common::Avatar; #[derive(Clone)] pub enum RoomEvent { - Invitation(OwnedUserId, OwnedUserId, bool), - Join(OwnedUserId, Option, Option, bool), + Invitation(OwnedUserId, OwnedUserId, bool, Span), + Join(OwnedUserId, Option, Option, bool, Span), - NewTopic(Option), - NewName(Option), - NewAvatar(Option), - NewChild(OwnedRoomId), + NewTopic(Option, Span), + NewName(Option, Span), + NewAvatar(Option, Span), + NewChild(OwnedRoomId, Span), } impl Debug for RoomEvent { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { match self { - Self::Invitation(invitee_id, sender_id, is_account_user) => f + Self::Invitation(invitee_id, sender_id, is_account_user, _span) => f .debug_tuple("RoomEvent::Invitation") .field(invitee_id) .field(sender_id) .field(is_account_user) .finish(), - Self::Join(user_id, user_name, avatar_url, is_account_user) => f + Self::Join(user_id, user_name, avatar_url, is_account_user, _span) => f .debug_tuple("RoomEvent::Join") .field(user_id) .field(user_name) .field(avatar_url) .field(is_account_user) .finish(), - Self::NewTopic(topic) => f.debug_tuple("RoomEvent::NewTopic").field(topic).finish(), - Self::NewName(name) => f.debug_tuple("RoomEvent::NewName").field(name).finish(), - Self::NewAvatar(avatar) => f + Self::NewTopic(topic, _span) => { + f.debug_tuple("RoomEvent::NewTopic").field(topic).finish() + } + Self::NewName(name, _span) => f.debug_tuple("RoomEvent::NewName").field(name).finish(), + Self::NewAvatar(avatar, _span) => f + // Self::NewAvatar(avatar) => f .debug_tuple("RoomEvent::NewAvatar") .field(&format!("is_some: {}", &avatar.is_some())) .finish(), - Self::NewChild(room_id) => f + Self::NewChild(room_id, _span) => f .debug_tuple("SpaceEvent::NewChild") .field(room_id) .finish(), diff --git a/src/main.rs b/src/main.rs index 67946ea..9739b50 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,10 @@ mod utils; use dioxus::prelude::*; use tracing::{debug, error}; + +use tracing_forest::ForestLayer; use tracing_subscriber::prelude::*; +use tracing_subscriber::EnvFilter; cfg_if! { if #[cfg(feature = "desktop")] { @@ -111,36 +114,34 @@ fn main() { let mut layers = Vec::new(); cfg_if! { - if #[cfg(feature = "desktop")] { - let config = Config::new().with_menu(None);; - builder = builder.with_cfg(config); - - let log_file = File::create("/tmp/bg92.log").unwrap(); - let file_layer = tracing_subscriber::fmt::layer() - .with_writer(log_file) - .with_timer(UtcTime::new(Iso8601::DATE_TIME)) - .with_ansi(false) - .with_filter(tracing::level_filters::LevelFilter::WARN) - .boxed(); - layers.push(file_layer); - - let console_layer = tracing_subscriber::fmt::layer() - // .with_filter(tracing::level_filters::LevelFilter::DEBUG); - .with_filter(tracing::level_filters::LevelFilter::WARN) - .boxed(); - layers.push(console_layer); - - } else if #[cfg(feature = "web")] { + if #[cfg(target_family = "wasm")] { let console_layer = tracing_subscriber::fmt::layer() .with_ansi(false) // Only partially supported across browsers .without_time() // std::time is not available in browsers, see note below .with_writer(MakeWebConsoleWriter::new()) // write events to the console - .with_filter(tracing::level_filters::LevelFilter::INFO); + .boxed(); + layers.push(console_layer); + } else { + let config = Config::new().with_menu(None); + builder = builder.with_cfg(config); + + let log_file = File::create("/tmp/bg92.log").unwrap(); + let file_layer = tracing_subscriber::fmt::layer() + .with_ansi(false) + .with_writer(log_file) + .with_timer(UtcTime::new(Iso8601::DATE_TIME)) + .boxed(); + layers.push(file_layer); + + let console_layer = ForestLayer::default().boxed(); layers.push(console_layer); } } - tracing_subscriber::registry().with(layers).init(); + tracing_subscriber::registry() + .with(layers) + .with(EnvFilter::from_default_env()) + .init(); builder.launch(app); } diff --git a/src/ui/store/room.rs b/src/ui/store/room.rs index a5cb542..6693084 100644 --- a/src/ui/store/room.rs +++ b/src/ui/store/room.rs @@ -35,7 +35,7 @@ pub struct Room { impl Room { pub fn signal(&self) -> Store { - self.store.borrow().clone() + *self.store.borrow() } pub fn from_domain(room: Rc) -> Self { diff --git a/src/ui/store/space.rs b/src/ui/store/space.rs index c476bae..1403d42 100644 --- a/src/ui/store/space.rs +++ b/src/ui/store/space.rs @@ -25,7 +25,7 @@ pub struct Space { impl Space { pub fn signal(&self) -> Store { - self.store.borrow().clone() + *self.store.borrow() } pub fn from_domain(space: Rc) -> Self { From f43f54c1207242ce03bff597a7de78137295183b Mon Sep 17 00:00:00 2001 From: Adrien Date: Tue, 21 May 2024 12:35:00 +0200 Subject: [PATCH 29/41] =?UTF-8?q?=F0=9F=9A=A7=20Use=20of=20local=20Dioxus?= =?UTF-8?q?=20repos=20waiting=20the=20#2338=20PR=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cf https://github.com/DioxusLabs/dioxus/pull/2338 --- Cargo.toml | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ff0d443..1be2323 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,25 +39,38 @@ cfg-if = "1.0.0" # Logging/tracing tracing = "0.1.40" -tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-forest = "0.1.6" # SCSS -> CSS + usage in rust code turf = "0.8.0" -# Matrix -matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false, features = ["rustls-tls"] } - # Dioxus -dioxus = { git = "https://github.com/ASR-ASU/dioxus.git", branch = "asr/add-scrolling-attributes" } -dioxus-free-icons = { git = "https://github.com/ASR-ASU/dioxus-free-icons.git", features = ["ionicons", "font-awesome-solid"] } +# dioxus-free-icons = { version = "0.8.*", features = ["ionicons", "font-awesome-solid"] } +dioxus-free-icons = { path = "../dioxuslabs/dioxus-free-icons/packages/lib", features = ["ionicons", "font-awesome-solid"] } + modx = "0.1.2" + [target.'cfg(target_family = "wasm")'.dependencies] -web-sys = "0.3.69" +# Logging/tracing +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-web = "0.1.3" +# Dioxus +# dioxus = { version = "0.5", features = ["web"] } +dioxus = { path = "../dioxuslabs/dioxus/packages/dioxus", features = ["web"] } +web-sys = "0.3.69" +# Matrix +matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false, features = ["rustls-tls", "js"] } [target.'cfg(not(target_family = "wasm"))'.dependencies] +# Utils time = "0.3.36" +# Logging/tracing +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time"] } +# Dioxus +# dioxus = { version = "0.5", features = ["desktop"] } +dioxus = { path = "../dioxuslabs/dioxus/packages/dioxus", features = ["desktop"] } +# Matrix +matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false, features = ["rustls-tls"] } [build-dependencies] regex = "1.10.3" From a1fe74f53e4a6bfff3d492bed60fb3941a7bc92a Mon Sep 17 00:00:00 2001 From: Adrien Date: Tue, 21 May 2024 12:38:40 +0200 Subject: [PATCH 30/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Use=20of=20"targe?= =?UTF-8?q?t=5Ffamily"=20instead=20of=20feature=20to=20manage=20wasm=20pla?= =?UTF-8?q?tform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 4 +- src/infrastructure/services/mozaik_builder.rs | 26 ++++++- .../services/random_svg_generators.rs | 73 ++++++++++--------- src/main.rs | 6 +- 4 files changed, 66 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1be2323..562a6b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,7 @@ version = "0.1.0" edition = "2021" [features] -default = ["web"] -desktop = ["dioxus/desktop", "tracing-subscriber/time"] -web = ["dioxus/web", "matrix-sdk/js"] +default = [] [dependencies] # Errors diff --git a/src/infrastructure/services/mozaik_builder.rs b/src/infrastructure/services/mozaik_builder.rs index d9d5375..fb946b4 100644 --- a/src/infrastructure/services/mozaik_builder.rs +++ b/src/infrastructure/services/mozaik_builder.rs @@ -6,6 +6,12 @@ use image::{DynamicImage, ImageFormat}; use image::{GenericImage, RgbImage}; use tracing::{error, warn}; +cfg_if! { + if #[cfg(not(target_family = "wasm"))] { + use tokio::task::spawn_blocking; + } +} + fn from_raw_to_image(raw: &Vec) -> Option { match Reader::new(Cursor::new(raw)).with_guessed_format() { Ok(reader) => match reader.decode() { @@ -19,7 +25,7 @@ fn from_raw_to_image(raw: &Vec) -> Option { None } -pub fn create_mozaik( +fn create_mozaik_( width_px: u32, height_px: u32, images: &[Vec], @@ -90,3 +96,21 @@ pub fn create_mozaik( bytes } + +pub async fn create_mozaik( + width_px: u32, + height_px: u32, + images: Vec>, + padding_image: Option>, +) -> Vec { + cfg_if! { + if #[cfg(target_family = "wasm")] { + create_mozaik_(width_px, height_px, &images, &padding_image) + } + else { + spawn_blocking(move || { + create_mozaik_(width_px, height_px, &images, &padding_image) + }).await.unwrap() + } + } +} diff --git a/src/infrastructure/services/random_svg_generators.rs b/src/infrastructure/services/random_svg_generators.rs index dd4faf3..2ca21e0 100644 --- a/src/infrastructure/services/random_svg_generators.rs +++ b/src/infrastructure/services/random_svg_generators.rs @@ -7,11 +7,13 @@ use rand::distributions::{Alphanumeric, DistString}; use reqwest::Result as RequestResult; use tracing::error; -#[cfg(feature = "desktop")] -use tokio::fs::read_to_string; - -#[cfg(feature = "web")] -use web_sys; +cfg_if! { + if #[cfg(target_family = "wasm")] { + use web_sys; + } else { + use tokio::fs::read_to_string; + } +} #[derive(Eq, PartialEq, Hash)] pub enum AvatarFeeling { @@ -145,39 +147,38 @@ async fn fetch_dicebear_svg( text.unwrap_or("".to_string()) } -#[cfg(feature = "desktop")] -fn gen_placeholder_fetcher(path: &'static str) -> Box>> { - let path = format!("./public/{}", &path); - Box::new(async move { - match read_to_string(&path).await { - Ok(content) => Some(content), - Err(err) => { - error!( - "Error during the access to the {path} file: {}", - err.to_string() - ); - None - } +cfg_if! { + if #[cfg(target_family = "wasm")] { + fn gen_placeholder_fetcher<'a>(path: &'static str) -> Box>> { + Box::new(async move { + let url = format!("{}{}", web_sys::window().unwrap().origin(), path); + match fetch_text(url).await { + Ok(content) => Some(content), + Err(err) => { + error!("Error during {path} fetching: {}", err.to_string()); + None + } + } + }) } - }) -} -#[cfg(feature = "web")] -fn gen_placeholder_fetcher<'a>(path: &'static str) -> Box>> { - Box::new(async move { - let url = format!("{}{}", web_sys::window().unwrap().origin(), path); - match fetch_text(url).await { - Ok(content) => Some(content), - Err(err) => { - error!("Error during {path} fetching: {}", err.to_string()); - None - } + } + else { + fn gen_placeholder_fetcher(path: &'static str) -> Box>> { + let path = format!("./public/{}", &path); + Box::new(async move { + match read_to_string(&path).await { + Ok(content) => Some(content), + Err(err) => { + error!( + "Error during the access to the {path} file: {}", + err.to_string() + ); + None + } + } + }) } - }) -} - -#[cfg(not(any(feature = "desktop", feature = "web")))] -fn gen_placeholder_fetcher<'a>(_path: &'static str) -> Box>> { - Box::new(async move { None }) + } } pub async fn generate_random_svg_avatar<'a>(config: Option<&'a AvatarConfig<'a>>) -> String { diff --git a/src/main.rs b/src/main.rs index 9739b50..562b69c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,13 +16,13 @@ use tracing_subscriber::prelude::*; use tracing_subscriber::EnvFilter; cfg_if! { - if #[cfg(feature = "desktop")] { + if #[cfg(target_family = "wasm")] { + use tracing_web::MakeWebConsoleWriter; + } else { use dioxus::desktop::Config; use std::fs::File; use time::format_description::well_known::Iso8601; use tracing_subscriber::fmt::time::UtcTime; - } else if #[cfg(feature = "web")] { - use tracing_web::MakeWebConsoleWriter; } } From 8c244ce4a70f7203efbd3967472e75e5b98e238c Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 22 May 2024 15:28:51 +0200 Subject: [PATCH 31/41] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Use=20dioxus=20ma?= =?UTF-8?q?in=20branch=20for=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 562a6b5..0050bd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,18 +43,19 @@ tracing-forest = "0.1.6" turf = "0.8.0" # Dioxus -# dioxus-free-icons = { version = "0.8.*", features = ["ionicons", "font-awesome-solid"] } -dioxus-free-icons = { path = "../dioxuslabs/dioxus-free-icons/packages/lib", features = ["ionicons", "font-awesome-solid"] } - +dioxus = { version = "0.5", default-features = false } +dioxus-free-icons = { version = "0.8", features = ["ionicons", "font-awesome-solid"] } modx = "0.1.2" +[patch.crates-io] +dioxus = { git = "https://github.com/DioxusLabs/dioxus.git" } + [target.'cfg(target_family = "wasm")'.dependencies] # Logging/tracing tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-web = "0.1.3" # Dioxus -# dioxus = { version = "0.5", features = ["web"] } -dioxus = { path = "../dioxuslabs/dioxus/packages/dioxus", features = ["web"] } +dioxus = { features = ["web"] } web-sys = "0.3.69" # Matrix matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false, features = ["rustls-tls", "js"] } @@ -65,8 +66,7 @@ time = "0.3.36" # Logging/tracing tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time"] } # Dioxus -# dioxus = { version = "0.5", features = ["desktop"] } -dioxus = { path = "../dioxuslabs/dioxus/packages/dioxus", features = ["desktop"] } +dioxus = { features = ["desktop"] } # Matrix matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false, features = ["rustls-tls"] } From 35e191eb6273b2e3fb7d87fa07913fcb22dff973 Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 22 May 2024 16:06:44 +0200 Subject: [PATCH 32/41] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20=20WorkerTask::Ge?= =?UTF-8?q?tRoomMembers=20isn't=20used,=20remove=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/messaging_interface.rs | 1 - src/domain/model/room.rs | 3 +-- src/infrastructure/messaging/matrix/client.rs | 23 ++----------------- .../messaging/matrix/requester.rs | 13 ----------- .../messaging/matrix/worker_tasks.rs | 12 +--------- 5 files changed, 4 insertions(+), 48 deletions(-) diff --git a/src/domain/model/messaging_interface.rs b/src/domain/model/messaging_interface.rs index 7f3ce4e..0ef1ace 100644 --- a/src/domain/model/messaging_interface.rs +++ b/src/domain/model/messaging_interface.rs @@ -44,7 +44,6 @@ pub trait RoomMessagingConsumerInterface { #[async_trait(?Send)] pub trait RoomMessagingProviderInterface { async fn get_avatar(&self, id: &RoomId) -> anyhow::Result>; - async fn get_members(&self, id: &RoomId) -> anyhow::Result>; } #[async_trait(?Send)] diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index eb98e4c..b0e1df0 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -7,8 +7,7 @@ use std::{ use async_trait::async_trait; use futures::future::{join, join_all}; -use matrix_sdk::ruma::OwnedRoomId; -use matrix_sdk::RoomState as MatrixRoomState; +use matrix_sdk::{ruma::OwnedRoomId, RoomState as MatrixRoomState}; use tracing::{debug, debug_span, error, instrument, trace}; use super::{ diff --git a/src/infrastructure/messaging/matrix/client.rs b/src/infrastructure/messaging/matrix/client.rs index 7209745..33f9eee 100644 --- a/src/infrastructure/messaging/matrix/client.rs +++ b/src/infrastructure/messaging/matrix/client.rs @@ -10,7 +10,7 @@ use matrix_sdk::{ config::SyncSettings, event_handler::Ctx, media::{MediaFormat, MediaRequest, MediaThumbnailSize}, - room::{ParentSpace, Room, RoomMember}, + room::{ParentSpace, Room}, ruma::{ api::client::media::get_content_thumbnail::v3::Method, events::{ @@ -26,7 +26,7 @@ use matrix_sdk::{ }, uint, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UserId, }, - Client as MatrixClient, RoomMemberships, RoomState, + Client as MatrixClient, RoomState, }; use tokio::sync::{ broadcast, @@ -702,21 +702,6 @@ impl Client { } } - async fn get_room_members(&mut self, room_id: &OwnedRoomId) -> anyhow::Result> { - let client = self.client.as_ref().unwrap(); - - if let Some(room) = client.get_room(room_id) { - match room.members(RoomMemberships::ACTIVE).await { - Ok(room_members) => Ok(room_members), - Err(err) => Err(err.into()), - } - } else { - warn!("No room found with the \"{}\" id", room_id.as_str()); - // TODO: Return an error if the room has not been found - Ok(vec![]) - } - } - // TODO: Share MediaRequest with other media requests async fn get_thumbnail(&self, media_url: OwnedMxcUri) -> anyhow::Result> { let client = self.client.as_ref().unwrap(); @@ -814,10 +799,6 @@ impl Client { WorkerTask::GetRoomAvatar(id, reply) => { reply.send(self.get_room_avatar(&id).await).await; } - WorkerTask::GetRoomMembers(id, reply) => { - reply.send(self.get_room_members(&id).await).await; - } - WorkerTask::GetRoomMemberAvatar(room_id, user_id, avatar_url, reply) => { reply .send( diff --git a/src/infrastructure/messaging/matrix/requester.rs b/src/infrastructure/messaging/matrix/requester.rs index 20e80c5..63bc10a 100644 --- a/src/infrastructure/messaging/matrix/requester.rs +++ b/src/infrastructure/messaging/matrix/requester.rs @@ -362,19 +362,6 @@ impl RoomMessagingProviderInterface for Requester { async fn get_avatar(&self, room_id: &RoomId) -> anyhow::Result> { request_to_worker!(self, WorkerTask::GetRoomAvatar, room_id.clone()) } - - // TODO: Fix return code - async fn get_members(&self, room_id: &RoomId) -> anyhow::Result> { - match request_to_worker!(self, WorkerTask::GetRoomMembers, room_id.clone()) { - Ok(matrix_room_members) => { - Ok(join_all(matrix_room_members.iter().map(|member| async { - RoomMember::from_matrix(member, room_id, Rc::new(self.clone())).await - })) - .await) - } - Err(err) => Err(err), - } - } } #[async_trait(?Send)] diff --git a/src/infrastructure/messaging/matrix/worker_tasks.rs b/src/infrastructure/messaging/matrix/worker_tasks.rs index 380d15a..74fdb0f 100644 --- a/src/infrastructure/messaging/matrix/worker_tasks.rs +++ b/src/infrastructure/messaging/matrix/worker_tasks.rs @@ -1,9 +1,6 @@ use std::fmt::{Debug, Formatter}; -use matrix_sdk::{ - room::RoomMember, - ruma::{OwnedMxcUri, OwnedRoomId, OwnedUserId}, -}; +use matrix_sdk::ruma::{OwnedMxcUri, OwnedRoomId, OwnedUserId}; use crate::utils::Sender; @@ -20,8 +17,6 @@ pub enum WorkerTask { GetAvatar(Sender>>>), GetRoomAvatar(OwnedRoomId, Sender>>>), - GetRoomMembers(OwnedRoomId, Sender>>), - GetRoomMemberAvatar( OwnedRoomId, OwnedUserId, @@ -60,11 +55,6 @@ impl Debug for WorkerTask { .debug_tuple("WorkerTask::GetRoomAvatar") .field(id) .finish(), - WorkerTask::GetRoomMembers(id, _) => f - .debug_tuple("WorkerTask::GetRoomMembers") - .field(id) - .finish(), - WorkerTask::GetRoomMemberAvatar(room_id, user_id, avatar_url, _) => f .debug_tuple("WorkerTask::GetRoomMemberAvatar") .field(room_id) From 19d64d7ac5027a8332e0bf258d6d29c7856232d2 Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 22 May 2024 16:42:20 +0200 Subject: [PATCH 33/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Render=20Room=20a?= =?UTF-8?q?vatar=20using=20the=20RoomMember=20ones,=20if=20not=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base.rs | 1 - src/domain/model/messaging_interface.rs | 6 +- src/domain/model/room.rs | 117 ++++++++++-------- src/domain/model/room_member.rs | 37 +++--- src/infrastructure/messaging/matrix/client.rs | 56 ++++----- .../messaging/matrix/requester.rs | 14 +-- .../messaging/matrix/worker_tasks.rs | 4 +- 7 files changed, 126 insertions(+), 109 deletions(-) diff --git a/src/base.rs b/src/base.rs index 0cc57e1..4d62c58 100644 --- a/src/base.rs +++ b/src/base.rs @@ -40,7 +40,6 @@ pub async fn login(mut rx: UnboundedReceiver, session: &GlobalSignal, + avatar_url: Option, + room_id: RoomId, + user_id: UserId, ) -> anyhow::Result>; } diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index b0e1df0..c47742b 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -79,11 +79,12 @@ impl PartialEq for Room { impl Room { pub fn new( id: RoomId, - spaces: Vec, + // TODO: move space at the end of the list of params name: Option, topic: Option, is_direct: Option, state: Option, + spaces: Vec, ) -> Self { Self { id, @@ -93,10 +94,8 @@ impl Room { is_direct, state, avatar: RefCell::new(None), - invitations: RefCell::new(HashMap::new()), members: RefCell::new(HashMap::new()), - spaces, messaging_provider: None, @@ -133,11 +132,6 @@ impl Room { self.topic = topic; } - #[allow(dead_code)] - pub fn is_direct(&self) -> &Option { - &self.is_direct - } - #[allow(dead_code)] pub fn state(&self) -> &Option { &self.state @@ -160,6 +154,29 @@ impl Room { store.on_invitation(invitation); } } + + #[instrument(name = "Room", skip_all)] + fn add_member(&self, member: RoomMember) { + let mut members = self.members.borrow_mut(); + + members.insert(member.id().clone(), member.clone()); + + // USe the member display name to name the room if it's direct and has no name set. + if self.name.borrow().is_none() && members.len() == 1 { + if let Some(member_display_name) = member.display_name() { + let name = Some(member_display_name.clone()); + + self.name.borrow_mut().clone_from(&name); + + if let Some(store) = self.store.borrow().as_ref() { + store.on_new_name(name); + } + } + } + + if let Some(store) = self.store.borrow().as_ref() { + store.on_new_member(member); + } } pub async fn get_avatar(&self) -> Option { @@ -171,7 +188,7 @@ impl Room { return Some(avatar); } else { debug!("The room has no avatar... let's generate one"); - match self.render_room_avatar_with_members().await { + match self.gen_room_avatar_with_members().await { Ok(avatar) => { if let Some(avatar) = avatar { return Some(avatar); @@ -190,50 +207,42 @@ impl Room { self.avatar.borrow().clone() } - async fn render_room_avatar_with_members(&self) -> anyhow::Result> { - if let Some(requester) = &self.messaging_provider { - match requester.get_members(&self.id).await { - Ok(members) => { - let mut account_member = None::<&RoomMember>; - let mut other_members = Vec::<&RoomMember>::new(); + #[instrument(name = "Room", skip_all)] + async fn gen_room_avatar_with_members(&self) -> anyhow::Result> { + let mut account_member = None::<&RoomMember>; + let mut other_members = Vec::<&RoomMember>::new(); - for member in &members { - if member.is_account_user() { - account_member = Some(member); - } else { - other_members.push(member); - } - } - - let other_avatars_futures = - join_all(other_members.iter().map(|member| member.get_avatar())); - - let (other_avatars, account_avatar) = if let Some(account_member) = - account_member - { - join(other_avatars_futures, account_member.get_avatar()).await - } else { - ( - join_all(other_members.iter().map(|member| member.get_avatar())).await, - None, - ) - }; - - let other_avatars: Vec> = other_avatars.into_iter().flatten().collect(); - - return Ok(Some(create_mozaik( - 256, - 256, - &other_avatars, - &account_avatar, - ))); - } - Err(err) => { - error!("err={}", err); - } + let members = self.members.borrow(); + for member in members.values() { + if member.is_account_user() { + account_member = Some(member); + } else { + other_members.push(member); } } - Ok(None) + + let other_avatars_futures = + join_all(other_members.iter().map(|member| member.get_avatar())); + + let (other_avatars, account_avatar) = if let Some(account_member) = account_member { + join(other_avatars_futures, account_member.get_avatar()).await + } else { + ( + join_all(other_members.iter().map(|member| member.get_avatar())).await, + None, + ) + }; + + let other_avatars: Vec> = other_avatars.into_iter().flatten().collect(); + + if account_avatar.is_some() || !other_avatars.is_empty() { + let _guard = debug_span!("AvatarRendering").entered(); + Ok(Some( + create_mozaik(256, 256, other_avatars, account_avatar).await, + )) + } else { + Ok(None) + } } } @@ -277,6 +286,10 @@ impl RoomMessagingConsumerInterface for Room { #[instrument(name = "Room", skip_all)] async fn on_new_avatar(&self, avatar: Option) { trace!("on_new_avatar"); + self.avatar.borrow_mut().clone_from(&avatar); + if let Some(store) = self.store.borrow().as_ref() { + store.on_new_avatar(avatar); + } } } @@ -294,6 +307,10 @@ impl RoomStoreConsumerInterface for Room { self.name.borrow().clone() } + async fn avatar(&self) -> Option { + self.get_avatar().await + } + fn spaces(&self) -> &Vec { &self.spaces } diff --git a/src/domain/model/room_member.rs b/src/domain/model/room_member.rs index 8714c74..ac7cdd2 100644 --- a/src/domain/model/room_member.rs +++ b/src/domain/model/room_member.rs @@ -4,7 +4,7 @@ use std::{ rc::Rc, }; -use matrix_sdk::{room::RoomMember as MatrixRoomMember, ruma::OwnedRoomId}; +use matrix_sdk::ruma::OwnedMxcUri; use tracing::error; use super::{ @@ -13,9 +13,14 @@ use super::{ room::RoomId, }; +pub type AvatarUrl = OwnedMxcUri; + #[derive(Clone)] pub struct RoomMember { id: UserId, + + display_name: Option, + avatar_url: Option, room_id: RoomId, is_account_user: bool, @@ -26,14 +31,18 @@ pub struct RoomMember { } impl RoomMember { - fn new( + pub fn new( id: UserId, + display_name: Option, + avatar_url: Option, room_id: RoomId, is_account_user: bool, messaging_provider: Rc, ) -> Self { Self { id, + display_name, + avatar_url, room_id, is_account_user, avatar: RefCell::new(None), @@ -41,24 +50,14 @@ impl RoomMember { } } - // TODO: Use a factory instead... - pub async fn from_matrix( - matrix_room_member: &MatrixRoomMember, - room_id: &OwnedRoomId, - messaging_provider: Rc, - ) -> Self { - Self::new( - UserId::from(matrix_room_member.user_id()), - room_id.clone(), - matrix_room_member.is_account_user(), - messaging_provider, - ) - } - pub fn id(&self) -> &UserId { &self.id } + pub fn display_name(&self) -> &Option { + &self.display_name + } + #[allow(dead_code)] pub fn room_id(&self) -> &RoomId { &self.room_id @@ -71,7 +70,11 @@ impl RoomMember { pub async fn get_avatar(&self) -> Option { match self .messaging_provider - .get_avatar(&self.room_id, &self.id) + .get_avatar( + self.avatar_url.clone(), + self.room_id.clone(), + self.id.clone(), + ) .await { Ok(avatar) => avatar, diff --git a/src/infrastructure/messaging/matrix/client.rs b/src/infrastructure/messaging/matrix/client.rs index 33f9eee..008ce76 100644 --- a/src/infrastructure/messaging/matrix/client.rs +++ b/src/infrastructure/messaging/matrix/client.rs @@ -723,41 +723,39 @@ impl Client { async fn get_room_member_avatar( &self, + avatar_url: &Option, room_id: &RoomId, user_id: &UserId, - avatar_url: &Option, ) -> anyhow::Result>> { let client = self.client.as_ref().unwrap(); if let Some(room) = client.get_room(room_id) { - // TODO: Check if we can get member before fetching the data and received an error... - - match room.get_member(user_id).await { - Ok(room_member) => { - if let Some(room_member) = room_member { - let res = match room_member - .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { - method: Method::Scale, - width: uint!(256), - height: uint!(256), - })) - .await - { - Ok(avatar) => Ok(avatar), - Err(err) => Err(err.into()), - }; - return res; - } + match avatar_url { + Some(avatar_url) => { + let thumbnail = self.get_thumbnail(avatar_url.clone()).await; + return Ok(Some(thumbnail?)); } - Err(err) => { - warn!("Unable to get room member {user_id}: {err}"); - if let Some(avatar_url) = avatar_url { - let thumbnail = self.get_thumbnail(avatar_url.clone()).await; - return Ok(Some(thumbnail?)); - } else { - debug!("No avatar url set for the {room_id} room"); + None => match room.get_member(user_id).await { + Ok(room_member) => { + if let Some(room_member) = room_member { + let res = match room_member + .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + })) + .await + { + Ok(avatar) => Ok(avatar), + Err(err) => Err(err.into()), + }; + return res; + } } - } + Err(err) => { + warn!("Unable to get room member {user_id}: {err}"); + } + }, } } Ok(None) @@ -799,10 +797,10 @@ impl Client { WorkerTask::GetRoomAvatar(id, reply) => { reply.send(self.get_room_avatar(&id).await).await; } - WorkerTask::GetRoomMemberAvatar(room_id, user_id, avatar_url, reply) => { + WorkerTask::GetRoomMemberAvatar(avatar_url, room_id, user_id, reply) => { reply .send( - self.get_room_member_avatar(&room_id, &user_id, &avatar_url) + self.get_room_member_avatar(&avatar_url, &room_id, &user_id) .await, ) .await; diff --git a/src/infrastructure/messaging/matrix/requester.rs b/src/infrastructure/messaging/matrix/requester.rs index 63bc10a..85f7f29 100644 --- a/src/infrastructure/messaging/matrix/requester.rs +++ b/src/infrastructure/messaging/matrix/requester.rs @@ -242,7 +242,7 @@ impl AccountMessagingProviderInterface for Requester { new_room_tx, span ) => { - let mut room = Room::new(id, spaces, name, topic, is_direct, Some(state)); + let mut room = Room::new(id, name, topic, is_direct, Some(state), spaces); let room_id = room.id().clone(); room.set_messaging_provider(client.clone()); @@ -371,16 +371,16 @@ impl SpaceMessagingProviderInterface for Requester {} impl MemberMessagingProviderInterface for Requester { async fn get_avatar( &self, - room_id: &RoomId, - user_id: &UserId, - avatar_url: &Option, + avatar_url: Option, + room_id: RoomId, + user_id: UserId, ) -> anyhow::Result> { request_to_worker!( self, WorkerTask::GetRoomMemberAvatar, - room_id.clone(), - user_id.clone(), - avatar_url.clone() + avatar_url, + room_id, + user_id ) } } diff --git a/src/infrastructure/messaging/matrix/worker_tasks.rs b/src/infrastructure/messaging/matrix/worker_tasks.rs index 74fdb0f..e8bb291 100644 --- a/src/infrastructure/messaging/matrix/worker_tasks.rs +++ b/src/infrastructure/messaging/matrix/worker_tasks.rs @@ -18,9 +18,9 @@ pub enum WorkerTask { GetRoomAvatar(OwnedRoomId, Sender>>>), GetRoomMemberAvatar( + Option, OwnedRoomId, OwnedUserId, - Option, Sender>>>, ), } @@ -57,9 +57,9 @@ impl Debug for WorkerTask { .finish(), WorkerTask::GetRoomMemberAvatar(room_id, user_id, avatar_url, _) => f .debug_tuple("WorkerTask::GetRoomMemberAvatar") + .field(avatar_url) .field(room_id) .field(user_id) - .field(avatar_url) .finish(), } } From e7e1a4d66337525764b0b3a3531b468388e522b4 Mon Sep 17 00:00:00 2001 From: Adrien Date: Thu, 23 May 2024 08:45:51 +0200 Subject: [PATCH 34/41] =?UTF-8?q?=F0=9F=92=84=20Use=20of=20border-box=20bo?= =?UTF-8?q?x=20sizing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes calculating the size of the UI components easier, especially those which have padding, margin or border. --- src/ui/_base.scss | 9 +++++++++ src/ui/components/_panel.scss | 5 +++-- src/ui/components/login.scss | 2 +- src/ui/components/modal.scss | 2 +- src/ui/components/text_input.scss | 9 +++------ 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/ui/_base.scss b/src/ui/_base.scss index 2ee904d..9bf236c 100644 --- a/src/ui/_base.scss +++ b/src/ui/_base.scss @@ -194,9 +194,18 @@ $transition-duration: 300ms; font-weight: bold; } +// Cf. https://css-tricks.com/box-sizing/ +html { + box-sizing: border-box; +} +*, *:before, *:after { + box-sizing: inherit; +} + body { height: 100vh; width: 100vw; + margin: 0px; padding: 0px; outline: 0px; diff --git a/src/ui/components/_panel.scss b/src/ui/components/_panel.scss index ef24da3..9dc3efd 100644 --- a/src/ui/components/_panel.scss +++ b/src/ui/components/_panel.scss @@ -5,8 +5,9 @@ $panel-aspect-ratio: 1/1.618; @mixin panel($padding-v: 2%, $padding-h: 2%) { padding: $padding-v $padding-h; - height: calc(100% - (2 * $padding-v) - (2 * $border-big-width)); - width: calc(100% - (2 * $padding-h) - (2 * $border-big-width)); + height: 100%; + width: 100%; + flex-shrink: 0; border: $border-big; diff --git a/src/ui/components/login.scss b/src/ui/components/login.scss index 0c23b7e..8953a3a 100644 --- a/src/ui/components/login.scss +++ b/src/ui/components/login.scss @@ -73,7 +73,7 @@ overflow: hidden; &__content { - height: calc(100% + (2 * $border-normal-width)); + height: 100%; aspect-ratio: 1; } } diff --git a/src/ui/components/modal.scss b/src/ui/components/modal.scss index c6db37a..6c7ae37 100644 --- a/src/ui/components/modal.scss +++ b/src/ui/components/modal.scss @@ -67,7 +67,7 @@ $modal-max-height: 55vh; border-radius: $border-radius; &__placeholder { - width: calc(100% + (2 * $border-normal-width)); + width: 100%; height: 100%; } diff --git a/src/ui/components/text_input.scss b/src/ui/components/text_input.scss index d83cba6..ce09044 100644 --- a/src/ui/components/text_input.scss +++ b/src/ui/components/text_input.scss @@ -3,17 +3,14 @@ %base-text-input { $horizontal-padding: 1vw; - height: calc(100% - (2 * $border-normal-width)); - width: calc(100% - (2 * $horizontal-padding)); + height: 100%; + width: 100%; border: $border-normal; border-color: get-color(primary, 90); border-radius: $border-radius; - padding-left: $horizontal-padding; - padding-right: $horizontal-padding; - padding-top: 0px; - padding-bottom: 0px; + padding: 0px $horizontal-padding; } %base-input { From 8ed4ff3f2a33aadfe7c99f412fdaea4df5b38f4e Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 24 May 2024 22:35:10 +0200 Subject: [PATCH 35/41] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Rename=20views=20?= =?UTF-8?q?->=20layouts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 4 ++-- src/ui/{views/login_view.rs => layouts/login.rs} | 8 ++++---- src/ui/{views/login_view.scss => layouts/login.scss} | 0 src/ui/layouts/mod.rs | 1 + src/ui/mod.rs | 2 +- src/ui/views/mod.rs | 1 - 6 files changed, 8 insertions(+), 8 deletions(-) rename src/ui/{views/login_view.rs => layouts/login.rs} (67%) rename src/ui/{views/login_view.scss => layouts/login.scss} (100%) create mode 100644 src/ui/layouts/mod.rs delete mode 100644 src/ui/views/mod.rs diff --git a/src/main.rs b/src/main.rs index 562b69c..3009a40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ use crate::base::{login, sync_rooms}; use crate::base::{APP_SETTINGS, ROOMS, SESSION}; use crate::ui::components::login::Login; use crate::ui::components::main_window::MainWindow; -use crate::ui::views::login_view::LoginView; +use crate::ui::layouts::login::Login; mod base; @@ -103,7 +103,7 @@ fn app() -> Element { } } else { rsx! { - LoginView {}, + Login {}, } } } diff --git a/src/ui/views/login_view.rs b/src/ui/layouts/login.rs similarity index 67% rename from src/ui/views/login_view.rs rename to src/ui/layouts/login.rs index 50d527d..807661b 100644 --- a/src/ui/views/login_view.rs +++ b/src/ui/layouts/login.rs @@ -1,11 +1,11 @@ use dioxus::prelude::*; -use crate::ui::components::login::Login; +use crate::ui::components::login::Login as LoginComponent; use crate::ui::components::wallpaper::Wallpaper; -turf::style_sheet!("src/ui/views/login_view.scss"); +turf::style_sheet!("src/ui/layouts/login.scss"); -pub fn LoginView() -> Element { +pub fn Login() -> Element { rsx! { style { {STYLE_SHEET} }, @@ -18,7 +18,7 @@ pub fn LoginView() -> Element { div { class: ClassName::LOGIN_VIEW_LOGIN_PANEL, - Login {} + LoginComponent {} } } } diff --git a/src/ui/views/login_view.scss b/src/ui/layouts/login.scss similarity index 100% rename from src/ui/views/login_view.scss rename to src/ui/layouts/login.scss diff --git a/src/ui/layouts/mod.rs b/src/ui/layouts/mod.rs new file mode 100644 index 0000000..8b4eddb --- /dev/null +++ b/src/ui/layouts/mod.rs @@ -0,0 +1 @@ +pub(crate) mod login; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 3d84e88..7c27e1d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,3 @@ pub(crate) mod components; +pub(crate) mod layouts; pub(crate) mod store; -pub(crate) mod views; diff --git a/src/ui/views/mod.rs b/src/ui/views/mod.rs deleted file mode 100644 index 1701ad6..0000000 --- a/src/ui/views/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod login_view; From ff0ac7f9823758580f6eb348f04addbfa6e14685 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 25 May 2024 14:06:40 +0200 Subject: [PATCH 36/41] =?UTF-8?q?=F0=9F=9A=A7=20Add=20a=20ChatPanel=20plac?= =?UTF-8?q?eholder=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/chat_panel.rs | 18 ++++++++++++++++++ src/ui/components/chat_panel.scss | 6 ++++++ 2 files changed, 24 insertions(+) create mode 100644 src/ui/components/chat_panel.rs create mode 100644 src/ui/components/chat_panel.scss diff --git a/src/ui/components/chat_panel.rs b/src/ui/components/chat_panel.rs new file mode 100644 index 0000000..0ba0b6c --- /dev/null +++ b/src/ui/components/chat_panel.rs @@ -0,0 +1,18 @@ +use dioxus::prelude::*; + +turf::style_sheet!("src/ui/components/chat_panel.scss"); + +#[component] +pub fn ChatPanel(name: String) -> Element { + rsx! { + style { {STYLE_SHEET} }, + + div { + class: ClassName::CHAT_PANEL, + + div { + {name} + } + } + } +} diff --git a/src/ui/components/chat_panel.scss b/src/ui/components/chat_panel.scss new file mode 100644 index 0000000..ce05d8a --- /dev/null +++ b/src/ui/components/chat_panel.scss @@ -0,0 +1,6 @@ +@import "../_base.scss"; +@import "./_panel.scss"; + +.chat-panel { + @include panel(); +} From 5194899de0778b2640063f5b25327dcf9b62a367 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 26 May 2024 10:45:09 +0200 Subject: [PATCH 37/41] =?UTF-8?q?=E2=9C=A8=20Add=20a=20first=20Conversatio?= =?UTF-8?q?ns=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/_base.scss | 3 + src/ui/components/conversations.rs | 502 +++++++++++++++++++++++++++ src/ui/components/conversations.scss | 305 ++++++++++++++++ 3 files changed, 810 insertions(+) create mode 100644 src/ui/components/conversations.rs create mode 100644 src/ui/components/conversations.scss diff --git a/src/ui/_base.scss b/src/ui/_base.scss index 9bf236c..a443cc3 100644 --- a/src/ui/_base.scss +++ b/src/ui/_base.scss @@ -174,6 +174,9 @@ $border-big-width: 4px; $border-big: solid $border-big-width $border-default-color; $border-normal-width: 2px; $border-normal: solid $border-normal-width $border-default-color; +$border-thin-width: 1px; +$border-thin: solid $border-thin-width $border-default-color; + // TODO: Radius should be a percentage(eg: 1024/16px). $border-radius: 16px; diff --git a/src/ui/components/conversations.rs b/src/ui/components/conversations.rs new file mode 100644 index 0000000..1b7d65f --- /dev/null +++ b/src/ui/components/conversations.rs @@ -0,0 +1,502 @@ +use base64::{engine::general_purpose, Engine as _}; +use dioxus::prelude::*; +use tracing::{debug, trace, warn}; + +use super::{button::Button, icons::SearchIcon, text_input::TextInput}; +use crate::{ + base::{ACCOUNT, STORE}, + domain::model::{common::PresenceState as DomainPresenceState, room::RoomId, space::SpaceId}, + ui::components::icons::{ChatsIcon, LogoIcon, RoomsIcon, SpacesIcon}, +}; + +turf::style_sheet!("src/ui/components/conversations.scss"); + +#[component] +fn AccountAvatar(content: Option>, class_name: Option) -> Element { + match content { + Some(content) => { + let encoded = general_purpose::STANDARD.encode(content); + rsx! { + div { + class: class_name, + background_image: format!("url(data:image/jpeg;base64,{encoded})") + } + } + } + // TODO: Manage acount without avatar + None => None, + } +} + +#[component] +fn PresenceState(state: Option, class_name: Option) -> Element { + let class_name = class_name.unwrap_or("".to_string()); + + match state { + Some(state) => { + let state_class = match state { + DomainPresenceState::Online => ClassName::ONLINE, + DomainPresenceState::Offline => ClassName::OFFLINE, + DomainPresenceState::Unavailable => ClassName::UNAVAILABLE, + _ => ClassName::UNAVAILABLE, + }; + + let classes = [class_name.as_str(), state_class].join(" "); + + rsx! { + div { + class: classes, + LogoIcon {}, + } + } + } + None => None, + } +} + +#[component] +fn DisplayName(display_name: Option, class_name: Option) -> Element { + match display_name { + Some(display_name) => { + rsx! { + div { + class: class_name, + p { + {display_name}, + } + } + } + } + None => None, + } +} + +#[component] +fn Status(status: Option, class_name: Option) -> Element { + let status = status.unwrap_or("Type your status".to_string()); + + rsx! { + div { + class: class_name, + TextInput { + placeholder: status, + } + } + } +} + +pub fn Account() -> Element { + let avatar = use_resource(move || async move { + let account = ACCOUNT.read(); + let avatar = account.get_avatar().await; + rsx! { + AccountAvatar { + class_name: ClassName::ACCOUNT_AVATAR, + content: avatar.borrow().clone(), + } + } + }); + + let presence_state = use_resource(move || async move { + // TODO: Fetch the state from the domain + rsx! { + PresenceState { + state: DomainPresenceState::Online, + class_name: ClassName::ACCOUNT_PRESENCE_STATE, + } + } + }); + + let display_name = use_resource(move || async move { + let account = ACCOUNT.read(); + let display_name = account.get_display_name().await; + rsx! { + DisplayName { + class_name: ClassName::ACCOUNT_DISPLAY_NAME, + display_name: display_name.borrow().clone(), + } + } + }); + + let status = use_resource(move || async move { + // TODO: Fetch the status from the domain + rsx! { + Status { + class_name: ClassName::ACCOUNT_STATUS, + status: "Coucou, Je suis BG92".to_string(), + } + } + }); + + rsx! { + style { {STYLE_SHEET} }, + + div { + class: ClassName::ACCOUNT, + + {avatar}, + {presence_state}, + {display_name}, + + {status}, + + div { + class: ClassName::ACCOUNT_SPACES, + Button { + SpacesIcon {}, + } + }, + + div { + class: ClassName::ACCOUNT_CHAT, + Button { + ChatsIcon {}, + } + }, + + div { + class: ClassName::ACCOUNT_ROOM, + Button { + RoomsIcon {}, + } + }, + } + } +} + +#[component] +pub fn ConversationAvatar(room_id: RoomId, on_clicked: EventHandler) -> Element { + let rooms = STORE.read().rooms(); + let toto = rooms.get(&room_id).unwrap(); + + let room = toto.signal(); + + let room_id = room.id(); + let room_name = room.name(); + + let selected_room_id = use_context::>>(); + + let invited_badge = if room.is_invited() { + rsx! { + div { + class: ClassName::CONVERSATION_AVATAR_INVITED_BADGE, + + p { + "Invited", + } + } + } + } else { + None + }; + + let is_selected = match selected_room_id.read().as_ref() { + Some(selected_room_id) => *selected_room_id == room_id, + None => false, + }; + + // let avatar = if let Some(Some(content)) = &*avatar.read() { + let avatar = if let Some(content) = room.avatar() { + let encoded = general_purpose::STANDARD.encode(content); + rsx! { + div { + class: ClassName::CONVERSATION_AVATAR_IMAGE, + background_image: format!("url(data:image/jpeg;base64,{encoded})"), + } + } + } else { + let placeholder = room_name + .unwrap_or("?".to_string()) + .to_uppercase() + .chars() + .next() + .unwrap_or('?') + .to_string(); + + debug!("Use of {} placeholder for {}", placeholder, room_id); + + rsx! { + div { + class: ClassName::CONVERSATION_AVATAR_IMAGE, + + {placeholder}, + } + } + }; + + let classes = [ + ClassName::CONVERSATION_AVATAR, + if is_selected { ClassName::SELECTED } else { "" }, + ]; + let classes_str = classes.join(" "); + + rsx! { + div { + class: "{classes_str}", + + onclick: move |evt| { + on_clicked.call(room_id.clone()); + evt.stop_propagation(); + }, + + {avatar} + {invited_badge} + } + } +} + +#[component] +pub fn ConversationsCarousel(on_selected_conversation: EventHandler) -> Element { + let mut ordered_rooms = use_signal(Vec::::new); + + use_effect(move || { + let rooms = use_context::>>(); + let rooms = rooms.read(); + for room in rooms.iter() { + if !ordered_rooms.peek().contains(room) { + ordered_rooms.push(room.clone()); + } + } + ordered_rooms.retain(|room| rooms.contains(room)); + }); + + let ordered_rooms = ordered_rooms.read(); + let rendered_avatars = ordered_rooms.iter().map(|room| { + rsx! { + ConversationAvatar { + room_id: room.clone(), + on_clicked: on_selected_conversation, + } + } + }); + + rsx! { + div { + class: ClassName::SPACE_CONVERSATIONS_CAROUSEL, + // TODO: Needed? + onscroll: move |_| { + // Catch scrolling events. + }, + {rendered_avatars}, + } + } +} + +#[component] +pub fn Space(id: SpaceId) -> Element { + let space = STORE.read().spaces().get(&id).unwrap().signal(); + + let name = space.name(); + + let mut selected_room_id = use_context_provider(|| Signal::new(None::)); + let mut displayed_rooms = use_context_provider(|| Signal::new(Vec::::new())); + + use_effect(move || { + // let rooms = STORE.read().rooms(); + let rooms = STORE.peek().rooms(); + let room_ids = space.room_ids(); + for room_id in room_ids { + if rooms.contains_key(&room_id) { + displayed_rooms.write().push(room_id); + } + } + }); + + let on_selected_conversation = move |room_id: RoomId| { + trace!(""); + selected_room_id.set(Some(room_id)); + }; + + let mut space_classes: [&str; 2] = [ClassName::SPACE, ""]; + + let mut selected_room_name = "".to_string(); + + if let Some(room_id) = selected_room_id.read().as_ref() { + space_classes[1] = ClassName::DISPLAY_CONVERSATION_NAME; + + if let Some(room) = STORE.read().rooms().get(room_id) { + let room = room.signal(); + + if let Some(name) = room.name() { + selected_room_name = name; + } else { + debug!("No name set for {} room", &room_id); + selected_room_name = room_id.to_string(); + } + } else { + warn!("No room found for the {} id", &room_id); + } + } + + let classes_str = space_classes.join(" "); + + rsx! { + style { {STYLE_SHEET} }, + + div { + class: "{classes_str}", + + // Deselect the conversation on clicks outside of the ConversationAvatar + onclick: move |_| { + selected_room_id.set(None); + }, + + div { + class: ClassName::SPACE_NAME, + p { + {name} + } + }, + ConversationsCarousel { + on_selected_conversation, + }, + div { + class: ClassName::SPACE_CONVERSATION_NAME, + p { + {selected_room_name}, + } + } + } + } +} + +#[component] +pub fn HomeSpace() -> Element { + let name = "Home"; + + let mut selected_room_id = use_context_provider(|| Signal::new(None::)); + let mut displayed_rooms = use_context_provider(|| Signal::new(Vec::::new())); + + use_effect(move || { + let rooms = STORE.read().rooms(); + for room in rooms.values() { + if room.signal().spaces().is_empty() { + let room_id = room.signal().id(); + displayed_rooms.write().push(room_id); + } + } + }); + + let on_selected_conversation = move |room_id: RoomId| { + selected_room_id.set(Some(room_id)); + }; + + let mut space_classes: [&str; 2] = [ClassName::SPACE, ""]; + + let mut selected_room_name = "".to_string(); + + if let Some(room_id) = selected_room_id.read().as_ref() { + space_classes[1] = ClassName::DISPLAY_CONVERSATION_NAME; + + if let Some(room) = STORE.read().rooms().get(room_id) { + let room = room.signal(); + + if let Some(name) = room.name() { + selected_room_name = name; + } else { + debug!("No name set for {} room", &room_id); + selected_room_name = room_id.to_string(); + } + } else { + warn!("No room found for the {} id", &room_id); + } + } + + let classes_str = space_classes.join(" "); + + rsx! { + style { {STYLE_SHEET} }, + + div { + class: "{classes_str}", + + // Deselect the conversation on clicks outside of the ConversationAvatar + onclick: move |_| { + selected_room_id.set(None); + }, + + div { + class: ClassName::SPACE_NAME, + p { + {name} + } + }, + ConversationsCarousel { + on_selected_conversation, + }, + div { + class: ClassName::SPACE_CONVERSATION_NAME, + p { + {selected_room_name}, + } + } + } + } +} + +pub fn Spaces() -> Element { + let spaces = STORE.read().spaces(); + let space_ids = spaces.keys().clone().last(); + + let rendered_spaces = space_ids.map(|id| { + rsx! { + Space { id: id.clone() } + } + }); + + rsx! { + style { {STYLE_SHEET} }, + + div { + class: ClassName::SPACES, + + {rendered_spaces}, + + HomeSpace {}, + } + } +} + +pub fn Search() -> Element { + rsx! { + style { {STYLE_SHEET} }, + + div { + class: ClassName::SEARCH, + + TextInput {} + + div { + class: ClassName::SEARCH_BUTTON, + Button { + SearchIcon {} + } + } + } + } +} + +pub fn Conversations() -> Element { + rsx! { + style { {STYLE_SHEET} }, + + div { + class: ClassName::CONVERSATIONS, + + div { + class: ClassName::CONVERSATIONS_ACCOUNT, + Account {}, + }, + + div { + class: ClassName::CONVERSATIONS_SPACES, + Spaces {}, + }, + + div { + class: ClassName::CONVERSATIONS_SEARCH, + Search {}, + }, + } + } +} diff --git a/src/ui/components/conversations.scss b/src/ui/components/conversations.scss new file mode 100644 index 0000000..c565a92 --- /dev/null +++ b/src/ui/components/conversations.scss @@ -0,0 +1,305 @@ +@import "../base.scss"; +@import "./_panel.scss"; +@import "./button.scss"; + +@mixin button-class { + button { + @include button(secondary, 90); + width: 100%; + max-height: 128px; + } +} + +.account { + $colum-spacing: 5%; + $col-width: 8.75%; + $button-width: 20%; + $button-height: calc(100% / 3); + $buttons-row-margin-top: 10%; + + height: 100%; + width: 100%; + + display: grid; + grid-template-columns: auto $colum-spacing repeat(2, calc($button-width/2)) $colum-spacing $button-width $colum-spacing $button-width; + grid-template-rows: 30% auto $button-height; + row-gap: 5%; + grid-template-areas: + "avatar . state name name name name name" + "avatar . status status status status status status" + "avatar . spaces spaces . chat . room" + ; + + &__avatar { + grid-area: avatar; + + display: flex; + align-items: center; + justify-content: center; + + border: $border-normal; + border-radius: $border-radius; + + background-size: cover; + background-position: center; + } + + &__presence-state { + grid-area: state; + + svg { + height: 100%; + width: 100%; + + stroke: get-color(greyscale, 90); + } + + &.online { + svg { + fill: get-color(primary, 100); + } + } + &.offline { + svg { + fill: get-color(ternary, 100); + } + } + &.unavailable { + svg { + fill: get-color(greyscale, 80); + } + } + } + + &__display-name { + grid-area: name; + + display: flex; + align-items: center; + justify-content: center; + + p { + font-size: 2.5vh; + margin: 0; + text-align: center; + } + } + + &__status { + grid-area: status; + } + + @mixin extra-marged-button() { + @include button-class(); + } + + &__spaces { + grid-area: spaces; + + @include extra-marged-button(); + } + + &__chat { + grid-area: chat; + + @include extra-marged-button(); + } + + &__room { + grid-area: room; + + @include extra-marged-button(); + } +} + +.spaces { + $gap: 1%; + $spaces-to-display: 5; + + width: 100%; + height: 100%; + + display: flex; + flex-direction: column; + gap: $gap; + + overflow-y: scroll; + // TODO: Manage android, Safari, ... + scrollbar-width: none; + + $space-height: calc((100% - (($spaces-to-display - 1) * (1%))) / $spaces-to-display); + + --space-height: #{$space-height}; +} + +.space { + $gap: 5%; + $vertical-padding: 1%; + $horizontal-padding: 1%; + + height: var(--space-height); + width: 100%; + + flex-shrink: 0; + + border: $border-normal; + border-color: get-color(primary, 100); + border-radius: $border-radius; + + padding: $vertical-padding $horizontal-padding; + + $name-height: 15%; + $conversation-name-height: 15%; + + display: grid; + + grid-template-columns: 100%; + grid-template-rows: $name-height $gap auto 0% 0%; + grid-template-areas: + "name" + "." + "conversations-carousel" + "." + "conversation-name" + ; + + transition: $transition-duration; + + &.display-conversation-name { + grid-template-rows: $name-height $gap auto $gap $conversation-name-height; + } + + cursor: default; + + p { + margin: 0; + } + + &__name { + grid-area: name; + + display: flex; + align-items: center; + justify-content: left; + + font-size: 2vh; + } + + &__conversations-carousel { + grid-area: conversations-carousel; + + display: flex; + flex-flow: row; + gap: 1%; + + overflow-x: scroll; + + // TODO: Manage android, Safari, ... + scrollbar-width: none; + } + + &__conversation-name { + grid-area: conversation-name; + + display: flex; + align-items: center; + justify-content: center; + + font-size: 2vh; + } +} + +.conversation-avatar { + height: 100%; + aspect-ratio: 1; + + flex-shrink: 0; + + border: $border-thin; + border-radius: $border-radius; + + overflow: hidden; + + filter: brightness(90%); + + &.selected { + filter: brightness(120%); + } + + &__image { + height: 100%; + width: 100%; + + background-size: cover; + + display: flex; + align-items: center; + justify-content: center; + + font-size: 6vh; + color: get-color(primary, 80); + } + + &__invited-badge { + $height: 20%; + + height: $height; + width: 100%; + + position: relative; + top: calc($height * -1); + + color: get-color(greyscale, 0); + + display: flex; + align-items: center; + justify-content: center; + + font-size: 1.5vh; + + background-color: get-color(ternary, 100); + } +} + +.search { + height: 100%; + width: 100%; + + display: flex; + gap: 5%; + + &__button { + @include button-class(); + + width: 20%; + + flex-shrink: 0; + } +} + +.conversations { + @include panel(); + + $gap: 1%; + $account-height: 15%; + $search-height: 5%; + + display: flex; + flex-direction: column; + justify-content: space-between; + gap: $gap; + + &__account { + height: $account-height; + max-height: 384px; + } + + &__spaces { + min-height: calc(100% - $account-height - $search-height - (2 * $gap)); + } + + &__search { + height: $search-height; + max-height: 128px; + } +} From df32faa8e6c8a7e1339a5cc8c40a48256a25f518 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 26 May 2024 10:54:02 +0200 Subject: [PATCH 38/41] =?UTF-8?q?=F0=9F=90=9B=20Dioxus=20Props=20shall=20i?= =?UTF-8?q?mplement=20PartialEq=20and=20Clone=20traits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/text_input.rs b/src/ui/components/text_input.rs index 1ecb3ef..07e4fcb 100644 --- a/src/ui/components/text_input.rs +++ b/src/ui/components/text_input.rs @@ -10,7 +10,7 @@ turf::style_sheet!("src/ui/components/text_input.scss"); pub trait InputPropsData {} #[derive(Props, Clone, PartialEq)] -pub struct InputProps { +pub struct InputProps { value: Option, placeholder: Option, oninput: Option>>, From c8e8e2da676c46abd3a7cfe60c5bdc1eab1dc9d9 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 26 May 2024 11:17:37 +0200 Subject: [PATCH 39/41] =?UTF-8?q?=E2=9C=A8=20Add=20a=20first=20Conversatio?= =?UTF-8?q?ns=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/layouts/conversations.rs | 252 ++++++++++++++++++++++++++++++ src/ui/layouts/conversations.scss | 136 ++++++++++++++++ src/ui/layouts/mod.rs | 1 + 3 files changed, 389 insertions(+) create mode 100644 src/ui/layouts/conversations.rs create mode 100644 src/ui/layouts/conversations.scss diff --git a/src/ui/layouts/conversations.rs b/src/ui/layouts/conversations.rs new file mode 100644 index 0000000..80cb846 --- /dev/null +++ b/src/ui/layouts/conversations.rs @@ -0,0 +1,252 @@ +use std::rc::Rc; + +use dioxus::prelude::*; +use futures::join; + +turf::style_sheet!("src/ui/layouts/conversations.scss"); + +use crate::ui::components::chat_panel::ChatPanel; +use crate::ui::components::conversations::Conversations as ConversationsComponent; +use crate::ui::components::wallpaper::Wallpaper; + +// TODO: Get from SCSS +const WIDGET_HEIGHT_RATIO: f64 = 0.95; +const ASPECT_RATIO: f64 = 1.0 / 1.618; + +async fn on_carousel_scroll( + parent_div: &Rc, + first_div: &Rc, + last_div: &Rc, +) { + let results = join!( + parent_div.get_scroll_offset(), + parent_div.get_scroll_size(), + last_div.get_client_rect() + ); + if let (Ok(offset), Ok(size), Ok(last_div_rect)) = results { + let left = offset.x; + let width = size.width; + // The left border of the first div has been exceeded, scrool to the last div. + if left <= 0.0 { + let _ = last_div.scroll_to(ScrollBehavior::Smooth).await; + } + // The left border of the last div has been exceeded, scrool to the first div. + else { + let last_div_width = last_div_rect.size.width; + let distance_to_tail = width - left - last_div_width; + + if distance_to_tail < 1.0 { + let first_div = first_div.as_ref(); //.unwrap(); + let _ = first_div.scroll_to(ScrollBehavior::Smooth).await; + } + } + } +} + +fn LayoutSmall() -> Element { + let mut carousel_div = use_signal(|| None::>); + let mut first_div = use_signal(|| None::>); + let mut last_div = use_signal(|| None::>); + + let conversation_panels_nb = 3; + let conversation_panels = (0..conversation_panels_nb + 1).map(|i| { + let inner = rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL_INNER, + ChatPanel { name: format!("CHAT #{i}") }, + } + }; + + if i == conversation_panels_nb { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL, + onmounted: move |cx: Event| last_div.set(Some(cx.data())), + {inner} + } + } + } else { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL, + {inner} + } + } + } + }); + + rsx! { + style { {STYLE_SHEET} }, + + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL, + + onmounted: move |cx| async move { + let data = cx.data(); + carousel_div.set(Some(data)); + }, + + onscroll: move |_| { + async move { + if let (Some(carousel_div), Some(first_div), Some(last_div)) = ( + carousel_div.read().as_ref(), + first_div.read().as_ref(), + last_div.read().as_ref(), + ) { + on_carousel_scroll(carousel_div, first_div, last_div).await; + } + } + }, + + div { + class: ClassName::CONVERSATIONS_VIEW_HEAD, + } + + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL_CONVERSATIONS_PANEL, + onmounted: move |cx| async move { + let data = cx.data(); + let _ = data.as_ref().scroll_to(ScrollBehavior::Smooth).await; + first_div.set(Some(data)); + }, + + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL_CONVERSATIONS_PANEL_INNER, + ConversationsComponent {}, + }, + }, + + {conversation_panels} + + div { + class: ClassName::CONVERSATIONS_VIEW_TAIL, + } + } + } +} + +fn LayoutBig() -> Element { + let mut carousel_div = use_signal(|| None::>); + let mut first_div = use_signal(|| None::>); + let mut last_div = use_signal(|| None::>); + + let conversation_panels_nb = 3; + let conversation_panels = (0..conversation_panels_nb + 1).map(|i| { + if i == 0 { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, + onmounted: move |cx| async move { + let data = cx.data(); + let _ = data.as_ref().scroll_to(ScrollBehavior::Smooth).await; + first_div.set(Some(data)); + }, + ChatPanel { name: format!("CHAT #{i}") }, + } + } + } else if i == conversation_panels_nb { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, + onmounted: move |cx: Event| last_div.set(Some(cx.data())), + ChatPanel { name: format!("CHAT #{i}") }, + } + } + } else { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, + ChatPanel { name: format!("CHAT #{i}") }, + } + } + } + }); + + rsx! { + style { {STYLE_SHEET} }, + + div { + class: ClassName::CONVERSATIONS_VIEW_BIG, + + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANEL, + + ConversationsComponent {}, + }, + + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS, + + onmounted: move |cx| async move { + let data = cx.data(); + carousel_div.set(Some(data)); + }, + + onscroll: move |_| { + async move { + if let (Some(carousel_div), Some(first_div), Some(last_div)) = ( + carousel_div.read().as_ref(), + first_div.read().as_ref(), + last_div.read().as_ref(), + ) { + on_carousel_scroll(carousel_div, first_div, last_div).await; + } + } + }, + + div { + class: ClassName::CONVERSATIONS_VIEW_HEAD, + } + + {conversation_panels} + + div { + class: ClassName::CONVERSATIONS_VIEW_TAIL, + } + + }, + } + } +} + +pub fn Conversations() -> Element { + let mut view_size = use_signal(|| None::<(f64, f64)>); + + // TODO: Make the layout reactive (on window resize) + let layout = { + move || { + if let Some((width, height)) = view_size.read().as_ref() { + let component_width = height * WIDGET_HEIGHT_RATIO * ASPECT_RATIO; + let breakpoint_width = component_width * 2_f64; + + if *width >= breakpoint_width { + return rsx! { LayoutBig {} }; + } + } + rsx! {LayoutSmall {}} + } + }(); + + rsx! { + style { {STYLE_SHEET} }, + + Wallpaper { + display_version: true + }, + + div { + class: ClassName::CONVERSATIONS_VIEW, + onmounted: move |cx| { + async move { + let data = cx.data(); + + if let Ok(client_rect) = data.get_client_rect().await { + view_size.set(Some((client_rect.size.width, client_rect.size.height))); + } + } + }, + + {layout} + } + } +} diff --git a/src/ui/layouts/conversations.scss b/src/ui/layouts/conversations.scss new file mode 100644 index 0000000..8844a62 --- /dev/null +++ b/src/ui/layouts/conversations.scss @@ -0,0 +1,136 @@ +@import "../_base.scss"; +@import "../components/_panel.scss"; + +.conversations-view-head { + height: 100%; + width: 1px; + + flex-shrink: 0; + scroll-snap-align: end; +} + +.conversations-view-tail { + height: 100%; + width: 1px; + + flex-shrink: 0; + scroll-snap-align: start; +} + +.conversations-view { + $height: 100vh; + $width: 100vw; + $conversations-panel-height: calc($height * 0.95); + $conversations-panel-width: calc($conversations-panel-height * $panel-aspect-ratio); + $gap: 1%; + $content-height: 95%; + $ratio: 2; + + height: 100%; + width: 100%; + + position: relative; + top: -100vh; + margin-bottom: -100vh; + + &__small { + scroll-snap-type: x mandatory; + + height: 100%; + width: 100%; + + display: flex; + flex-direction: row; + gap: $gap; + + justify-content: safe center; + align-items: safe center; + + overflow-x: scroll; + + &__conversations-panel { + height: $content-height; + width: 100%; + + flex-shrink: 0; + scroll-snap-align: center; + + display: flex; + align-items: center; + justify-content: center; + + &__inner { + height: 100%; + + // TODO: Is aspect-ratio the best criteria to defined that inner shall take all the available space ? + @media (max-aspect-ratio: $panel-aspect-ratio) { + width: 100%; + } + @media (min-aspect-ratio: $panel-aspect-ratio) { + aspect-ratio: $panel-aspect-ratio; + } + } + } + + &__panel { + flex-shrink: 0; + + height: $content-height; + width: 100%; + + display: flex; + align-items: center; + justify-content: center; + + scroll-snap-align: center; + + &__inner { + height: 100%; + width: calc(100% - (2 * $gap)); + } + } + } + + &__big { + height: 100%; + width: 100%; + width: calc(100% - (2 * $gap)); + + display: flex; + flex-direction: row; + align-items: safe center; + gap: $gap; + + margin: 0 $gap; + + &__conversations-panel { + height: $content-height; + aspect-ratio: $panel-aspect-ratio; + } + + &__conversation-panels { + height: $content-height; + + flex-grow: 1; + + display: flex; + flex-direction: row; + overflow-x: scroll; + + justify-content: safe center; + align-items: safe center; + scroll-snap-type: x mandatory; + + gap: $gap; + + &__panel { + flex-shrink: 0; + + height: 100%; + width: 100%; + + scroll-snap-align: center; + } + } + } +} diff --git a/src/ui/layouts/mod.rs b/src/ui/layouts/mod.rs index 8b4eddb..1d1f310 100644 --- a/src/ui/layouts/mod.rs +++ b/src/ui/layouts/mod.rs @@ -1 +1,2 @@ +pub(crate) mod conversations; pub(crate) mod login; From 62015f8d131cf60f48ea25b7eac169430e34cdaa Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 26 May 2024 11:39:18 +0200 Subject: [PATCH 40/41] =?UTF-8?q?=E2=9C=A8=20Use=20of=20Conversations=20la?= =?UTF-8?q?yout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base.rs | 71 ---------------- src/main.rs | 131 ++++++++++++++--------------- src/ui/components/conversations.rs | 6 +- src/ui/components/login.rs | 23 +++-- src/ui/mod.rs | 10 +++ 5 files changed, 88 insertions(+), 153 deletions(-) delete mode 100644 src/base.rs diff --git a/src/base.rs b/src/base.rs deleted file mode 100644 index 4d62c58..0000000 --- a/src/base.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::rc::Rc; - -use dioxus::prelude::*; -use futures_util::stream::StreamExt; -use tracing::{debug, error, warn}; - -use crate::domain::model::account::Account; -use crate::domain::model::messaging_interface::AccountMessagingProviderInterface; -use crate::domain::model::session::Session; -use crate::infrastructure::messaging::matrix::client::Client; -use crate::infrastructure::messaging::matrix::worker_tasks::LoginStyle; -use crate::ui::store::Store; - -pub async fn login(mut rx: UnboundedReceiver, session: &GlobalSignal) { - while let Some(is_logged) = rx.next().await { - error!("is_logged={is_logged}"); - if !is_logged { - let homeserver_url = session.read().homeserver_url.clone(); - let username = session.read().username.clone(); - let password = session.read().password.clone(); - - if homeserver_url.is_some() && username.is_some() && password.is_some() { - let (requester, account_events_receiver) = - Client::spawn(homeserver_url.unwrap()).await; - - if let Err(err) = requester.init().await { - error!("Following error occureds during client init: {}", err); - } - - error!("Before login"); - - match requester - .login(LoginStyle::Password(username.unwrap(), password.unwrap())) - .await - { - Ok(_) => { - debug!("successfully logged"); - session.write().is_logged = true; - - let requester = Rc::new(requester); - - dioxus::prelude::spawn(async move { - ACCOUNT.write().set_messaging_provider(requester.clone()); - - let _ = requester - .run_forever(&*ACCOUNT.read(), account_events_receiver) - .await; - }); - } - Err(err) => { - error!("Error during login: {err}"); - // TODO: Handle invalid login - // invalid_login.modify(|_| true); - return; - } - } - } else { - warn!("At least one of the following values is/are invalid: homeserver, username or password"); - } - } else { - warn!("already logged... skip login"); - } - } - error!("=== LOGIN END ==="); -} - -pub static STORE: GlobalSignal = Signal::global(Store::new); - -// TODO: Merge ACCOUNT and SESSION -pub static ACCOUNT: GlobalSignal = Signal::global(|| Account::new(&STORE)); -pub static SESSION: GlobalSignal = Signal::global(Session::new); diff --git a/src/main.rs b/src/main.rs index 3009a40..c29d3f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,12 +8,22 @@ mod infrastructure; mod ui; mod utils; -use dioxus::prelude::*; -use tracing::{debug, error}; +use std::rc::Rc; +use dioxus::prelude::*; +use futures_util::stream::StreamExt; +use tracing::{debug, error, warn}; use tracing_forest::ForestLayer; -use tracing_subscriber::prelude::*; -use tracing_subscriber::EnvFilter; +use tracing_subscriber::{prelude::*, EnvFilter}; + +use crate::{ + domain::model::{messaging_interface::AccountMessagingProviderInterface, session::Session}, + infrastructure::messaging::matrix::{client::Client, worker_tasks::LoginStyle}, + ui::{ + layouts::{conversations::Conversations, login::Login}, + ACCOUNT, SESSION, + }, +}; cfg_if! { if #[cfg(target_family = "wasm")] { @@ -26,80 +36,65 @@ cfg_if! { } } -use crate::base::{login, sync_rooms}; -use crate::base::{APP_SETTINGS, ROOMS, SESSION}; -use crate::ui::components::login::Login; -use crate::ui::components::main_window::MainWindow; -use crate::ui::layouts::login::Login; +async fn login(mut rx: UnboundedReceiver, session: &GlobalSignal) { + while let Some(is_logged) = rx.next().await { + if !is_logged { + let homeserver_url = session.read().homeserver_url.clone(); + let username = session.read().username.clone(); + let password = session.read().password.clone(); -mod base; + if homeserver_url.is_some() && username.is_some() && password.is_some() { + let (requester, account_events_receiver) = + Client::spawn(homeserver_url.unwrap()).await; + + if let Err(err) = requester.init().await { + warn!("Unable to login: {}", err); + } + + match requester + .login(LoginStyle::Password(username.unwrap(), password.unwrap())) + .await + { + Ok(_) => { + debug!("successfully logged"); + session.write().is_logged = true; + + let requester = Rc::new(requester); + + dioxus::prelude::spawn(async move { + ACCOUNT.write().set_messaging_provider(requester.clone()); + + let _ = requester + .run_forever(&*ACCOUNT.read(), account_events_receiver) + .await; + }); + } + Err(err) => { + error!("Error during login: {err}"); + // TODO: Handle invalid login + // invalid_login.modify(|_| true); + return; + } + } + } else { + warn!("At least one of the following values is/are invalid: homeserver, username or password"); + } + } else { + warn!("already logged... skip login"); + } + } +} fn app() -> Element { - debug!("*** App rendering ***"); - - let login_coro = use_coroutine(|rx| login(rx, &APP_SETTINGS, &SESSION)); - - let mut sync_rooms_coro = None; - - if let Some(requester) = &APP_SETTINGS.read().requester { - sync_rooms_coro = Some(use_coroutine(|rx| { - sync_rooms(rx, requester.borrow().receivers.clone(), &ROOMS) - })); - } + let login_coro = use_coroutine(|rx| login(rx, &SESSION)); if !SESSION.read().is_logged { login_coro.send(false); - } else { - if let Some(coro) = sync_rooms_coro { - coro.send(true); - } - - // if chats_win_state.read().is_none() { - // let chats_window = dioxus_desktop::use_window(cx); - - // let receivers = app_settings - // .read() - // .requester - // .as_ref() - // .unwrap() - // .borrow() - // .receivers - // .clone(); - - // let chats_props = ChatsWindowProps { - // receivers, - // interface: chats_win_interface_ref.clone(), - // }; - - // let chats_dom = VirtualDom::new_with_props(ChatsWindow, chats_props); - - // let window_cfg = Config::default().with_custom_head( - // r#" - // - // "# - // .to_owned(), - // ); - - // let chats_window_desktop_service = chats_window.new_window(chats_dom, window_cfg); - // chats_win_state.set(Some(chats_window_desktop_service)); - // } } if SESSION.read().is_logged { - debug!("Should render the MainWindow component"); rsx! { - MainWindow {}, + Conversations {} } } else { rsx! { diff --git a/src/ui/components/conversations.rs b/src/ui/components/conversations.rs index 1b7d65f..597c2fe 100644 --- a/src/ui/components/conversations.rs +++ b/src/ui/components/conversations.rs @@ -4,9 +4,11 @@ use tracing::{debug, trace, warn}; use super::{button::Button, icons::SearchIcon, text_input::TextInput}; use crate::{ - base::{ACCOUNT, STORE}, domain::model::{common::PresenceState as DomainPresenceState, room::RoomId, space::SpaceId}, - ui::components::icons::{ChatsIcon, LogoIcon, RoomsIcon, SpacesIcon}, + ui::{ + components::icons::{ChatsIcon, LogoIcon, RoomsIcon, SpacesIcon}, + ACCOUNT, STORE, + }, }; turf::style_sheet!("src/ui/components/conversations.scss"); diff --git a/src/ui/components/login.rs b/src/ui/components/login.rs index 012654c..fce17f0 100644 --- a/src/ui/components/login.rs +++ b/src/ui/components/login.rs @@ -1,7 +1,4 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; +use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc}; use const_format::formatcp; use dioxus::prelude::*; @@ -9,16 +6,18 @@ use tracing::{debug, error, warn}; use validator::{Validate, ValidateArgs, ValidateEmail, ValidationError, ValidationErrors}; use zxcvbn::zxcvbn; -use crate::base::SESSION; -use crate::domain::model::session::Session; -use crate::infrastructure::services::random_svg_generators::{ - generate_random_svg_shape, ShapeConfig, +use crate::{ + domain::model::session::Session, + infrastructure::services::random_svg_generators::{generate_random_svg_shape, ShapeConfig}, + ui::SESSION, }; -use super::button::{LoginButton, RegisterButton}; -use super::modal::{Modal, Severity}; -use super::spinner::Spinner; -use super::text_input::{PasswordInputState, PasswordTextInput, TextInput, TextInputState}; +use super::{ + button::{LoginButton, RegisterButton}, + modal::{Modal, Severity}, + spinner::Spinner, + text_input::{PasswordInputState, PasswordTextInput, TextInput, TextInputState}, +}; include!(concat!(env!("OUT_DIR"), "/style_vars.rs")); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 7c27e1d..d3dc2dd 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,13 @@ pub(crate) mod components; pub(crate) mod layouts; pub(crate) mod store; + +use dioxus::prelude::{GlobalSignal, Signal}; + +use super::domain::model::{account::Account, session::Session}; +use store::Store; + +pub static STORE: GlobalSignal = Signal::global(Store::new); +// TODO: Merge ACCOUNT and SESSION +pub static ACCOUNT: GlobalSignal = Signal::global(|| Account::new(&STORE)); +pub static SESSION: GlobalSignal = Signal::global(Session::new); From 89473cfd61046b7c060c2f11cc1231675e139cd3 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 26 May 2024 11:53:47 +0200 Subject: [PATCH 41/41] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20some=20clippy=20warn?= =?UTF-8?q?ings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/account.rs | 1 + src/domain/model/room.rs | 1 + src/domain/model/space.rs | 1 + src/domain/model/store_interface.rs | 3 +++ src/ui/store/room.rs | 3 +++ src/ui/store/space.rs | 2 ++ 6 files changed, 11 insertions(+) diff --git a/src/domain/model/account.rs b/src/domain/model/account.rs index e5aab71..642c829 100644 --- a/src/domain/model/account.rs +++ b/src/domain/model/account.rs @@ -52,6 +52,7 @@ impl Account { self.messaging_provider = Some(provider.clone()); } + #[allow(dead_code)] pub fn get_room(&self, room_id: &RoomId) -> Option> { self.by_id_rooms.borrow().get(room_id).cloned() } diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index c47742b..d9089a2 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -118,6 +118,7 @@ impl Room { &self.id } + #[allow(dead_code)] pub fn name(&self) -> Option { self.name.borrow().clone() } diff --git a/src/domain/model/space.rs b/src/domain/model/space.rs index 2037a53..3974faa 100644 --- a/src/domain/model/space.rs +++ b/src/domain/model/space.rs @@ -65,6 +65,7 @@ impl Space { &self.id } + #[allow(dead_code)] pub fn name(&self) -> Option { self.name.borrow().clone() } diff --git a/src/domain/model/store_interface.rs b/src/domain/model/store_interface.rs index 2077d05..3cc5747 100644 --- a/src/domain/model/store_interface.rs +++ b/src/domain/model/store_interface.rs @@ -28,7 +28,10 @@ pub trait RoomStoreConsumerInterface { fn id(&self) -> &RoomId; fn is_direct(&self) -> Option; fn name(&self) -> Option; + + #[allow(dead_code)] async fn avatar(&self) -> Option; + fn spaces(&self) -> &Vec; } diff --git a/src/ui/store/room.rs b/src/ui/store/room.rs index 6693084..413ec93 100644 --- a/src/ui/store/room.rs +++ b/src/ui/store/room.rs @@ -30,6 +30,8 @@ pub struct Store { #[derive(Clone)] pub struct Room { store: RefCell, + + #[allow(dead_code)] domain: Rc, } @@ -52,6 +54,7 @@ impl Room { } } + #[allow(dead_code)] pub async fn get_avatar(&self) -> Option { self.domain.avatar().await } diff --git a/src/ui/store/space.rs b/src/ui/store/space.rs index 1403d42..deff902 100644 --- a/src/ui/store/space.rs +++ b/src/ui/store/space.rs @@ -20,6 +20,8 @@ pub struct Store { #[derive(Clone)] pub struct Space { store: RefCell, + + #[allow(dead_code)] domain: Rc, }