🚧 Add an interface to the ChatsWindows to drive its behavior

For now, only the ChatsWindow tabs are toggled on clicks on room names (from ContactsSection).
This commit is contained in:
2023-12-30 23:31:51 +01:00
parent 2fed770f62
commit 116bbcb247
9 changed files with 208 additions and 92 deletions

View File

@@ -3,7 +3,7 @@
// (used by [UnboundedReceiver]) by adding 'futures_util' as a dependency to your project // (used by [UnboundedReceiver]) by adding 'futures_util' as a dependency to your project
// and adding the use futures_util::stream::StreamExt; // and adding the use futures_util::stream::StreamExt;
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use std::{cell::RefCell, collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use dioxus::prelude::*; use dioxus::prelude::*;
use fermi::*; use fermi::*;
@@ -15,9 +15,9 @@ use matrix_sdk::{
use tokio::select; use tokio::select;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use crate::components::chats_window::interface::Interface as ChatsWinInterface;
use crate::matrix_interface::client::{Client, RoomTopicEvent}; use crate::matrix_interface::client::{Client, RoomTopicEvent};
use crate::matrix_interface::requester::Receivers; use crate::matrix_interface::requester::{Receivers, Requester};
use crate::matrix_interface::requester::Requester;
use crate::matrix_interface::worker_tasks::LoginStyle; use crate::matrix_interface::worker_tasks::LoginStyle;
// #[derive(Clone, Debug)] // #[derive(Clone, Debug)]
@@ -41,7 +41,7 @@ use crate::matrix_interface::worker_tasks::LoginStyle;
// } // }
// } // }
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct Room { pub struct Room {
pub matrix_room: Arc<MatrixRoom>, pub matrix_room: Arc<MatrixRoom>,
pub topic: Option<RefCell<String>>, pub topic: Option<RefCell<String>>,
@@ -186,10 +186,7 @@ pub async fn login(
app_settings_ref: UseAtomRef<AppSettings>, app_settings_ref: UseAtomRef<AppSettings>,
session_ref: UseAtomRef<Session>, session_ref: UseAtomRef<Session>,
) { ) {
error!("=== LOGIN BEG ===");
while let Some(is_logged) = rx.next().await { while let Some(is_logged) = rx.next().await {
error!("State updated");
if !is_logged { if !is_logged {
let homeserver_url = session_ref.read().homeserver_url.clone(); let homeserver_url = session_ref.read().homeserver_url.clone();
let username = session_ref.read().username.clone(); let username = session_ref.read().username.clone();
@@ -198,9 +195,12 @@ pub async fn login(
if homeserver_url.is_some() && username.is_some() && password.is_some() { if homeserver_url.is_some() && username.is_some() && password.is_some() {
let client = Client::spawn(homeserver_url.unwrap()).await; let client = Client::spawn(homeserver_url.unwrap()).await;
client.init(); client.init().await;
match client.login(LoginStyle::Password(username.unwrap(), password.unwrap())) { match client
.login(LoginStyle::Password(username.unwrap(), password.unwrap()))
.await
{
Ok(_) => { Ok(_) => {
debug!("successfully logged"); debug!("successfully logged");
session_ref.write().is_logged = true; session_ref.write().is_logged = true;
@@ -221,8 +221,6 @@ pub async fn login(
error!("=== LOGIN END ==="); error!("=== LOGIN END ===");
} }
pub static ROOMS: AtomRef<ByIdRooms> = AtomRef(|_| ByIdRooms::new());
pub struct Session { pub struct Session {
pub homeserver_url: Option<String>, pub homeserver_url: Option<String>,
pub username: Option<String>, pub username: Option<String>,
@@ -250,4 +248,6 @@ impl Session {
} }
} }
pub static ROOMS: AtomRef<ByIdRooms> = AtomRef(|_| ByIdRooms::new());
pub static SESSION: AtomRef<Session> = AtomRef(|_| Session::new()); pub static SESSION: AtomRef<Session> = AtomRef(|_| Session::new());
pub static CHATS_WIN_INTERFACE: AtomRef<ChatsWinInterface> = AtomRef(|_| ChatsWinInterface::new());

View File

@@ -0,0 +1,32 @@
use dioxus::prelude::*;
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<Tasks>,
receiver: RefCell<Receiver<Tasks>>,
}
impl Interface {
pub fn new() -> Self {
let (sender, receiver) = channel::<Tasks>(32);
Self {
sender,
receiver: RefCell::new(receiver),
}
}
pub(super) fn receiver(&self) -> &RefCell<Receiver<Tasks>> {
&self.receiver
}
pub fn toggle_room(&self, room_id: OwnedRoomId) -> Result<usize, SendError<Tasks>> {
self.sender.send(Tasks::ToggleRoom(room_id))
}
}

View File

@@ -1,56 +1,115 @@
pub mod interface;
mod edit_section; mod edit_section;
use std::collections::{HashMap, HashSet};
use dioxus::prelude::*; use dioxus::prelude::*;
use fermi::*; use fermi::*;
use tracing::debug; use matrix_sdk::ruma::OwnedRoomId;
use tokio::sync::broadcast::Receiver;
use tracing::{debug, error};
use crate::base::{sync_rooms, ROOMS}; use crate::base::{sync_rooms, Room, ROOMS};
use crate::components::avatar_selector::AvatarSelector; use crate::components::avatar_selector::AvatarSelector;
use crate::components::icons::DownArrowIcon; use crate::components::icons::DownArrowIcon;
use crate::matrix_interface::requester::Receivers; use crate::matrix_interface::requester::Receivers;
use edit_section::EditSection; use edit_section::EditSection;
use interface::{Interface, Tasks};
turf::style_sheet!("src/components/chats_window/chats_window.scss"); turf::style_sheet!("src/components/chats_window/chats_window.scss");
pub struct ChatsWindowProps { pub struct ChatsWindowProps {
pub receivers: Receivers, pub receivers: Receivers,
pub interface: UseAtomRef<Interface>,
}
fn render_rooms_tabs<'a>(
rooms_atom_ref: &'a UseAtomRef<HashMap<OwnedRoomId, RefCell<Room>>>,
displayed_room_ids_ref: &'a UseRef<HashSet<OwnedRoomId>>,
) -> Vec<LazyNodes<'a, 'a>> {
let rooms_ref = rooms_atom_ref.read();
let displayed_room_ids = displayed_room_ids_ref.read();
rooms_ref
.values()
.filter(|room| displayed_room_ids.contains(&room.borrow().id()))
.map(|room| -> LazyNodes {
let room = room.borrow();
let room_name = room.name().unwrap_or(room.id().to_string());
rsx!(
div {
class: ClassName::TAB,
button {
img {
src: "./images/status_online.png",
},
"{room_name}",
},
},
)
})
.collect()
}
async fn handle_controls<'a>(
receiver_ref: &'a RefCell<Receiver<Tasks>>,
displayed_room_ids_ref: &'a UseRef<HashSet<OwnedRoomId>>,
) {
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_ref.write();
match displayed_room_ids.take(&room_id) {
Some(_) => {
error!("Toggle {} already dispayed... close it", room_id);
}
None => {
displayed_room_ids.insert(room_id);
}
}
}
Tasks::Test(msg) => error!("TEST {}", msg),
},
Err(err) => error!("{}", err),
}
}
} }
pub fn ChatsWindow(cx: Scope<ChatsWindowProps>) -> Element { pub fn ChatsWindow(cx: Scope<ChatsWindowProps>) -> Element {
debug!("ChatsWindow rendering"); debug!("ChatsWindow rendering");
let receivers = &cx.props.receivers;
use_init_atom_root(cx); use_init_atom_root(cx);
let receivers = &cx.props.receivers;
let interface_ref = &cx.props.interface;
let rooms_ref = use_atom_ref(cx, &ROOMS); let rooms_ref = use_atom_ref(cx, &ROOMS);
let displayed_room_ids = use_ref(cx, HashSet::<OwnedRoomId>::new);
let sync_rooms_coro = use_coroutine(cx, |rx| { let sync_rooms_coro = use_coroutine(cx, |rx| {
to_owned![receivers]; to_owned![receivers];
sync_rooms(rx, receivers, rooms_ref.clone()) sync_rooms(rx, receivers, rooms_ref.clone())
}); });
sync_rooms_coro.send(true); sync_rooms_coro.send(true);
let rooms = rooms_ref.read(); let _: &Coroutine<()> = use_coroutine(cx, |_: UnboundedReceiver<_>| {
let rendered_room_tabs = rooms.values().map(|room| { to_owned![interface_ref, displayed_room_ids];
let room = room.borrow(); async move {
let room_name = room.name().unwrap_or(room.id().to_string()); let interface = interface_ref.read();
rsx!( let receiver = &interface.receiver();
div { handle_controls(receiver, &displayed_room_ids).await
class: ClassName::TAB, }
button {
img {
src: "./images/status_online.png",
},
"{room_name}",
},
},
)
}); });
let rendered_rooms_tabs = render_rooms_tabs(rooms_ref, displayed_room_ids);
cx.render(rsx! { cx.render(rsx! {
style { STYLE_SHEET }, style { STYLE_SHEET },
@@ -60,7 +119,7 @@ pub fn ChatsWindow(cx: Scope<ChatsWindowProps>) -> Element {
div { div {
class: ClassName::TABS, class: ClassName::TABS,
rendered_room_tabs.into_iter(), rendered_rooms_tabs.into_iter(),
}, },
div { div {

View File

@@ -1,13 +1,12 @@
use std::cell::RefCell;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::icons::io_icons::IoChevronDown; use dioxus_free_icons::icons::io_icons::IoChevronDown;
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
use fermi::prelude::*; use fermi::prelude::*;
use matrix_sdk::RoomState; use matrix_sdk::{ruma::OwnedRoomId, RoomState};
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::base::{ByIdRooms, Room, ROOMS}; use crate::base::{ByIdRooms, Room, CHATS_WIN_INTERFACE, ROOMS};
use crate::components::chats_window::interface::Interface as ChatsWindowInterface;
turf::style_sheet!("src/components/contacts_window/contacts_section.scss"); turf::style_sheet!("src/components/contacts_window/contacts_section.scss");
@@ -24,7 +23,7 @@ fn ContactsArrow(cx: Scope) -> Element {
static NO_NAME_REPR: &str = "No name"; static NO_NAME_REPR: &str = "No name";
static NO_SUBJECT_REPR: &str = "No subject"; static NO_SUBJECT_REPR: &str = "No subject";
pub fn filter_people_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<RefCell<Room>> { pub(super) fn filter_people_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<RefCell<Room>> {
let rooms = rooms_atom.read(); let rooms = rooms_atom.read();
let mut filtered_rooms = Vec::<RefCell<Room>>::with_capacity(rooms.len()); let mut filtered_rooms = Vec::<RefCell<Room>>::with_capacity(rooms.len());
@@ -37,7 +36,7 @@ pub fn filter_people_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<Ref
filtered_rooms filtered_rooms
} }
pub fn filter_room_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<RefCell<Room>> { pub(super) fn filter_room_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<RefCell<Room>> {
let rooms = rooms_atom.read(); let rooms = rooms_atom.read();
let mut filtered_rooms = Vec::<RefCell<Room>>::with_capacity(rooms.len()); let mut filtered_rooms = Vec::<RefCell<Room>>::with_capacity(rooms.len());
@@ -50,6 +49,14 @@ pub fn filter_room_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<RefCe
filtered_rooms filtered_rooms
} }
// TODO: Handle errors
fn on_clicked_room(
room_id: &OwnedRoomId,
chats_window_interface: &UseAtomRef<ChatsWindowInterface>,
) {
let _ = chats_window_interface.read().toggle_room(room_id.clone());
}
#[component] #[component]
pub fn ContactsSection<'a>( pub fn ContactsSection<'a>(
cx: Scope, cx: Scope,
@@ -58,8 +65,10 @@ pub fn ContactsSection<'a>(
) -> Element { ) -> Element {
debug!("ContactsSection rendering"); debug!("ContactsSection rendering");
let rooms_atom = use_atom_ref(cx, &ROOMS); let rooms_atom_ref = use_atom_ref(cx, &ROOMS);
let contacts = filter(rooms_atom.clone()); let chats_window_interface_ref = use_atom_ref(cx, &CHATS_WIN_INTERFACE);
let contacts = filter(rooms_atom_ref.clone());
let contacts_len = contacts.len(); let contacts_len = contacts.len();
let show = use_state(cx, || false); let show = use_state(cx, || false);
@@ -80,6 +89,7 @@ pub fn ContactsSection<'a>(
.borrow() .borrow()
.to_owned(); .to_owned();
let room_name = room.name().unwrap_or(NO_NAME_REPR.to_string()); let room_name = room.name().unwrap_or(NO_NAME_REPR.to_string());
let room_id = room.id();
let is_invited = room.matrix_room.state() == RoomState::Invited; let is_invited = room.matrix_room.state() == RoomState::Invited;
@@ -93,6 +103,8 @@ pub fn ContactsSection<'a>(
); );
rsx!(li { rsx!(li {
onclick: move |_| on_clicked_room(&room_id, chats_window_interface_ref),
img { img {
src: "./images/status_online.png", src: "./images/status_online.png",
}, },

View File

@@ -1,14 +1,15 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
pub mod components;
pub mod matrix_interface;
pub mod utils;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::Config; use dioxus_desktop::Config;
use fermi::*; use fermi::*;
use tracing::{debug, Level}; use tracing::{debug, Level};
pub mod components; use crate::base::{login, sync_rooms, APP_SETTINGS, CHATS_WIN_INTERFACE, ROOMS, SESSION};
pub mod matrix_interface;
use crate::base::{login, sync_rooms, APP_SETTINGS, ROOMS, SESSION};
use crate::components::chats_window::{ChatsWindow, ChatsWindowProps}; use crate::components::chats_window::{ChatsWindow, ChatsWindowProps};
use crate::components::main_window::MainWindow; use crate::components::main_window::MainWindow;
@@ -22,6 +23,7 @@ fn App(cx: Scope) -> Element {
let app_settings_ref = use_atom_ref(cx, &APP_SETTINGS); let app_settings_ref = use_atom_ref(cx, &APP_SETTINGS);
let session_ref = use_atom_ref(cx, &SESSION); let session_ref = use_atom_ref(cx, &SESSION);
let rooms_ref = use_atom_ref(cx, &ROOMS); let rooms_ref = use_atom_ref(cx, &ROOMS);
let chats_win_interface_ref = use_atom_ref(cx, &CHATS_WIN_INTERFACE);
let chats_win_state = use_state(cx, || None); let chats_win_state = use_state(cx, || None);
@@ -55,7 +57,11 @@ fn App(cx: Scope) -> Element {
.borrow() .borrow()
.receivers .receivers
.clone(); .clone();
let chats_props = ChatsWindowProps { receivers };
let chats_props = ChatsWindowProps {
receivers,
interface: chats_win_interface_ref.clone(),
};
let chats_dom = VirtualDom::new_with_props(ChatsWindow, chats_props); let chats_dom = VirtualDom::new_with_props(ChatsWindow, chats_props);

View File

@@ -1,8 +1,7 @@
use std::cell::RefCell;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use dioxus::prelude::to_owned; use dioxus::prelude::*;
use tokio::sync::broadcast::Sender; use tokio::sync::broadcast::Sender;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
use tokio::sync::{broadcast, oneshot}; use tokio::sync::{broadcast, oneshot};
@@ -157,12 +156,10 @@ impl Client {
async fn on_original_sync_room_member_event( async fn on_original_sync_room_member_event(
_ev: OriginalSyncRoomMemberEvent, _ev: OriginalSyncRoomMemberEvent,
room: MatrixRoom, _room: MatrixRoom,
_client: MatrixClient, _client: MatrixClient,
) { ) {
debug!("== on_original_sync_room_member_event =="); debug!("== on_original_sync_room_member_event ==");
let room_id = room.room_id();
dbg!(room_id);
// let mut store = store_ctx.read().unwrap().to_owned(); // let mut store = store_ctx.read().unwrap().to_owned();
// dbg!(store.rooms.keys()); // dbg!(store.rooms.keys());
@@ -403,11 +400,11 @@ impl Client {
WorkerTask::Init(reply) => { WorkerTask::Init(reply) => {
assert!(!self.initialized); assert!(!self.initialized);
self.init(); self.init();
reply.send(()); reply.send(()).await;
} }
WorkerTask::Login(style, reply) => { WorkerTask::Login(style, reply) => {
assert!(self.initialized); assert!(self.initialized);
reply.send(self.login_and_sync(style).await); reply.send(self.login_and_sync(style).await).await;
} }
} }
} }

View File

@@ -1,15 +1,15 @@
use std::cell::RefCell;
use std::sync::Arc; use std::sync::Arc;
use dioxus::prelude::*;
use matrix_sdk::Client as MatrixClient; use matrix_sdk::Client as MatrixClient;
use tokio::sync::broadcast::Receiver; use tokio::sync::broadcast::Receiver;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use super::client::RoomTopicEvent; use super::client::RoomTopicEvent;
use super::worker_tasks::{oneshot, LoginStyle, WorkerTask}; use super::worker_tasks::{LoginStyle, WorkerTask};
use crate::base::Room; use crate::base::Room;
use crate::utils::oneshot;
#[derive(Debug)]
pub struct Receivers { pub struct Receivers {
pub rooms_receiver: RefCell<Receiver<Room>>, pub rooms_receiver: RefCell<Receiver<Room>>,
pub room_topic_receiver: RefCell<Receiver<RoomTopicEvent>>, pub room_topic_receiver: RefCell<Receiver<RoomTopicEvent>>,
@@ -24,7 +24,6 @@ impl Clone for Receivers {
} }
} }
#[derive(Debug)]
pub struct Requester { pub struct Requester {
pub matrix_client: Arc<MatrixClient>, pub matrix_client: Arc<MatrixClient>,
pub tx: UnboundedSender<WorkerTask>, pub tx: UnboundedSender<WorkerTask>,
@@ -32,15 +31,23 @@ pub struct Requester {
} }
impl Requester { impl Requester {
pub fn init(&self) { pub async fn init(&self) -> anyhow::Result<()> {
let (reply, response) = oneshot(); let (reply, mut response) = oneshot();
// TODO: Handle error case.
self.tx.send(WorkerTask::Init(reply)).unwrap(); self.tx.send(WorkerTask::Init(reply)).unwrap();
response.recv() match response.recv().await {
Some(result) => Ok(result),
None => Err(anyhow::Error::msg("TBD")),
}
} }
pub fn login(&self, style: LoginStyle) -> anyhow::Result<()> { pub async fn login(&self, style: LoginStyle) -> anyhow::Result<()> {
let (reply, response) = oneshot(); let (reply, mut response) = oneshot();
// TODO: Handle error case.
self.tx.send(WorkerTask::Login(style, reply)).unwrap(); self.tx.send(WorkerTask::Login(style, reply)).unwrap();
response.recv() match response.recv().await {
Some(result) => result,
None => Err(anyhow::Error::msg("TBD")),
}
} }
} }

View File

@@ -1,31 +1,6 @@
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; use crate::utils::Sender;
pub struct ClientResponse<T>(Receiver<T>);
pub struct ClientReply<T>(SyncSender<T>);
impl<T> ClientResponse<T> {
pub(super) fn recv(self) -> T {
self.0
.recv()
.expect("failed to receive response from client thread")
}
}
impl<T> ClientReply<T> {
pub(super) fn send(self, t: T) {
self.0.send(t).unwrap();
}
}
pub(super) fn oneshot<T>() -> (ClientReply<T>, ClientResponse<T>) {
let (tx, rx) = sync_channel(1);
let reply = ClientReply(tx);
let response = ClientResponse(rx);
(reply, response)
}
#[derive(Debug)] #[derive(Debug)]
pub enum LoginStyle { pub enum LoginStyle {
@@ -36,9 +11,9 @@ pub enum LoginStyle {
pub enum WorkerTask { pub enum WorkerTask {
// Init(AsyncProgramStore, ClientReply<()>), // Init(AsyncProgramStore, ClientReply<()>),
// Init(ClientReply<()>), // Init(ClientReply<()>),
Init(ClientReply<()>), Init(Sender<()>),
//Login(LoginStyle, ClientReply<EditInfo>), //Login(LoginStyle, ClientReply<EditInfo>),
Login(LoginStyle, ClientReply<anyhow::Result<()>>), Login(LoginStyle, Sender<anyhow::Result<()>>),
} }
impl Debug for WorkerTask { impl Debug for WorkerTask {

28
src/utils.rs Normal file
View File

@@ -0,0 +1,28 @@
use tokio::sync::mpsc::{channel, Receiver as _Receiver, Sender as _Sender};
pub struct Receiver<T>(_Receiver<T>);
impl<T> Receiver<T> {
pub(super) async fn recv(&mut self) -> Option<T> {
self.0.recv().await
}
}
pub struct Sender<T>(_Sender<T>);
// TODO: Handle error
impl<T> Sender<T> {
pub(super) async fn send(self, t: T) {
// self.0.send(t).unwrap();
let _ = self.0.send(t).await;
}
}
// TODO: Rename the name of the following fn
pub fn oneshot<T>() -> (Sender<T>, Receiver<T>) {
let (tx, rx) = channel(32);
let sender = Sender(tx);
let receiver = Receiver(rx);
(sender, receiver)
}