fractal/src/session/view/create_dm_dialog/dm_user_list.rs

204 lines
6.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::ruma::{api::client::user_directory::search_users, UserId};
use tracing::error;
use super::DmUser;
use crate::{prelude::*, session::model::Session, spawn, spawn_tokio};
#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "ContentDmUserListState")]
pub enum DmUserListState {
#[default]
Initial = 0,
Loading = 1,
NoMatching = 2,
Matching = 3,
Error = 4,
}
mod imp {
use std::cell::{Cell, RefCell};
use futures_util::future::AbortHandle;
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::DmUserList)]
pub struct DmUserList {
pub list: RefCell<Vec<DmUser>>,
/// The current session.
#[property(get, construct_only)]
pub session: glib::WeakRef<Session>,
/// The state of the list.
#[property(get, builder(DmUserListState::default()))]
pub state: Cell<DmUserListState>,
/// The search term.
#[property(get, set = Self::set_search_term, explicit_notify, nullable)]
pub search_term: RefCell<Option<String>>,
pub abort_handle: RefCell<Option<AbortHandle>>,
}
#[glib::object_subclass]
impl ObjectSubclass for DmUserList {
const NAME: &'static str = "DmUserList";
type Type = super::DmUserList;
type Interfaces = (gio::ListModel,);
}
#[glib::derived_properties]
impl ObjectImpl for DmUserList {}
impl ListModelImpl for DmUserList {
fn item_type(&self) -> glib::Type {
DmUser::static_type()
}
fn n_items(&self) -> u32 {
self.list.borrow().len() as u32
}
fn item(&self, position: u32) -> Option<glib::Object> {
self.list
.borrow()
.get(position as usize)
.cloned()
.and_upcast()
}
}
impl DmUserList {
/// Set the search term.
fn set_search_term(&self, search_term: Option<String>) {
let search_term = search_term.filter(|s| !s.is_empty());
if search_term.as_ref() == self.search_term.borrow().as_ref() {
return;
}
let obj = self.obj();
self.search_term.replace(search_term);
spawn!(clone!(@weak obj => async move {
obj.search_users().await;
}));
obj.notify_search_term();
}
}
}
glib::wrapper! {
/// List of users matching the `search term`.
pub struct DmUserList(ObjectSubclass<imp::DmUserList>)
@implements gio::ListModel;
}
impl DmUserList {
pub fn new(session: &Session) -> Self {
glib::Object::builder().property("session", session).build()
}
/// Set the state of the list.
fn set_state(&self, state: DmUserListState) {
let imp = self.imp();
if state == self.state() {
return;
}
imp.state.set(state);
self.notify("state");
}
fn set_list(&self, users: Vec<DmUser>) {
let added = users.len();
let prev_users = self.imp().list.replace(users);
self.items_changed(0, prev_users.len() as u32, added as u32);
}
fn clear_list(&self) {
self.set_list(Vec::new());
}
async fn search_users(&self) {
let Some(session) = self.session() else {
return;
};
let client = session.client();
let Some(search_term) = self.search_term() else {
self.set_state(DmUserListState::Initial);
return;
};
self.set_state(DmUserListState::Loading);
self.clear_list();
let search_term_clone = search_term.clone();
let handle = spawn_tokio!(async move { client.search_users(&search_term_clone, 20).await });
let (future, handle) = futures_util::future::abortable(handle);
if let Some(abort_handle) = self.imp().abort_handle.replace(Some(handle)) {
abort_handle.abort();
}
let response = if let Ok(result) = future.await {
result.unwrap()
} else {
return;
};
if Some(&search_term) != self.search_term().as_ref() {
return;
}
match response {
Ok(mut response) => {
let mut add_custom = false;
// If the search term looks like a UserId and is not already in the response,
// insert it.
if let Ok(user_id) = UserId::parse(&search_term) {
if !response.results.iter().any(|item| item.user_id == user_id) {
let user = search_users::v3::User::new(user_id);
response.results.insert(0, user);
add_custom = true;
}
}
let mut users: Vec<DmUser> = vec![];
for item in response.results.into_iter() {
let user = DmUser::new(
&session,
&item.user_id,
item.display_name.as_deref(),
item.avatar_url.as_deref(),
);
// If it is the "custom user" from the search term, fetch the avatar
// and display name
if add_custom && user.user_id() == search_term {
user.load_profile();
}
users.push(user);
}
match users.is_empty() {
true => self.set_state(DmUserListState::NoMatching),
false => self.set_state(DmUserListState::Matching),
}
self.set_list(users);
}
Err(error) => {
error!("Couldnt load matching users: {error}");
self.set_state(DmUserListState::Error);
self.clear_list();
}
}
}
}