183 lines
5.8 KiB
Rust
183 lines
5.8 KiB
Rust
use adw::subclass::prelude::*;
|
|
use gtk::{gdk, glib, glib::clone, prelude::*, CompositeTemplate};
|
|
|
|
mod dm_user;
|
|
mod dm_user_list;
|
|
mod dm_user_row;
|
|
|
|
use self::{
|
|
dm_user::DmUser,
|
|
dm_user_list::{DmUserList, DmUserListState},
|
|
dm_user_row::DmUserRow,
|
|
};
|
|
use crate::{
|
|
gettext,
|
|
session::model::{Session, User},
|
|
spawn, Window,
|
|
};
|
|
|
|
mod imp {
|
|
use glib::subclass::InitializingObject;
|
|
|
|
use super::*;
|
|
|
|
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
|
|
#[template(resource = "/org/gnome/Fractal/ui/session/view/create_dm_dialog/mod.ui")]
|
|
#[properties(wrapper_type = super::CreateDmDialog)]
|
|
pub struct CreateDmDialog {
|
|
/// The current session.
|
|
#[property(get, set = Self::set_session, explicit_notify)]
|
|
pub session: glib::WeakRef<Session>,
|
|
#[template_child]
|
|
pub list_box: TemplateChild<gtk::ListBox>,
|
|
#[template_child]
|
|
pub search_entry: TemplateChild<gtk::SearchEntry>,
|
|
#[template_child]
|
|
pub stack: TemplateChild<gtk::Stack>,
|
|
#[template_child]
|
|
pub error_page: TemplateChild<adw::StatusPage>,
|
|
}
|
|
|
|
#[glib::object_subclass]
|
|
impl ObjectSubclass for CreateDmDialog {
|
|
const NAME: &'static str = "CreateDmDialog";
|
|
type Type = super::CreateDmDialog;
|
|
type ParentType = adw::Window;
|
|
|
|
fn class_init(klass: &mut Self::Class) {
|
|
DmUserRow::static_type();
|
|
Self::bind_template(klass);
|
|
Self::Type::bind_template_callbacks(klass);
|
|
|
|
klass.add_binding_action(
|
|
gdk::Key::Escape,
|
|
gdk::ModifierType::empty(),
|
|
"window.close",
|
|
None,
|
|
);
|
|
}
|
|
|
|
fn instance_init(obj: &InitializingObject<Self>) {
|
|
obj.init_template();
|
|
}
|
|
}
|
|
|
|
#[glib::derived_properties]
|
|
impl ObjectImpl for CreateDmDialog {}
|
|
|
|
impl WidgetImpl for CreateDmDialog {}
|
|
impl WindowImpl for CreateDmDialog {}
|
|
impl AdwWindowImpl for CreateDmDialog {}
|
|
|
|
impl CreateDmDialog {
|
|
/// Set the current session.
|
|
pub fn set_session(&self, session: Option<Session>) {
|
|
if self.session.upgrade() == session {
|
|
return;
|
|
}
|
|
let obj = self.obj();
|
|
|
|
if let Some(session) = &session {
|
|
let user_list = DmUserList::new(session);
|
|
|
|
// We don't need to disconnect this signal since the `DmUserList` will be
|
|
// disposed once unbound from the `gtk::ListBox`
|
|
user_list.connect_state_notify(clone!(@weak obj => move |model| {
|
|
obj.update_view(model);
|
|
}));
|
|
|
|
self.search_entry
|
|
.bind_property("text", &user_list, "search-term")
|
|
.sync_create()
|
|
.build();
|
|
|
|
self.list_box.bind_model(Some(&user_list), |user| {
|
|
DmUserRow::new(
|
|
user.downcast_ref::<DmUser>()
|
|
.expect("DmUserList must contain only `DmUser`"),
|
|
)
|
|
.upcast()
|
|
});
|
|
|
|
obj.update_view(&user_list);
|
|
} else {
|
|
self.list_box.unbind_model();
|
|
}
|
|
|
|
self.session.set(session.as_ref());
|
|
obj.notify_session();
|
|
}
|
|
}
|
|
}
|
|
|
|
glib::wrapper! {
|
|
/// Dialog to create a new direct chat.
|
|
pub struct CreateDmDialog(ObjectSubclass<imp::CreateDmDialog>)
|
|
@extends gtk::Widget, gtk::Window, adw::Window, adw::Bin, @implements gtk::Accessible;
|
|
}
|
|
|
|
#[gtk::template_callbacks]
|
|
impl CreateDmDialog {
|
|
pub fn new(parent_window: Option<&impl IsA<gtk::Window>>, session: &Session) -> Self {
|
|
glib::Object::builder()
|
|
.property("transient-for", parent_window)
|
|
.property("session", session)
|
|
.build()
|
|
}
|
|
|
|
fn update_view(&self, model: &DmUserList) {
|
|
let visible_child_name = match model.state() {
|
|
DmUserListState::Initial => "no-search-page",
|
|
DmUserListState::Loading => "loading-page",
|
|
DmUserListState::NoMatching => "no-matching-page",
|
|
DmUserListState::Matching => "matching-page",
|
|
DmUserListState::Error => {
|
|
self.show_error(&gettext("An error occurred while searching for users"));
|
|
return;
|
|
}
|
|
};
|
|
|
|
self.imp().stack.set_visible_child_name(visible_child_name);
|
|
}
|
|
|
|
fn show_error(&self, message: &str) {
|
|
self.imp().error_page.set_description(Some(message));
|
|
self.imp().stack.set_visible_child_name("error-page");
|
|
}
|
|
|
|
#[template_callback]
|
|
fn row_activated_cb(&self, row: gtk::ListBoxRow) {
|
|
let Some(user) = row.downcast_ref::<DmUserRow>().and_then(|r| r.user()) else {
|
|
return;
|
|
};
|
|
|
|
// TODO: For now we show the loading page while we create the room,
|
|
// ideally we would like to have the same behavior as Element:
|
|
// Create the room only once the user sends a message
|
|
let imp = self.imp();
|
|
imp.stack.set_visible_child_name("loading-page");
|
|
imp.search_entry.set_sensitive(false);
|
|
|
|
spawn!(clone!(@weak self as obj, @weak user => async move {
|
|
obj.start_direct_chat(&user).await;
|
|
}));
|
|
}
|
|
|
|
async fn start_direct_chat(&self, user: &DmUser) {
|
|
match user.upcast_ref::<User>().get_or_create_direct_chat().await {
|
|
Ok(room) => {
|
|
let Some(window) = self.transient_for().and_downcast::<Window>() else {
|
|
return;
|
|
};
|
|
|
|
window.session_view().select_room(Some(room));
|
|
self.close();
|
|
}
|
|
Err(_) => {
|
|
self.show_error(&gettext("Failed to create a new Direct Chat"));
|
|
self.imp().search_entry.set_sensitive(true);
|
|
}
|
|
}
|
|
}
|
|
}
|