Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/project-robius/robrix into …
Browse files Browse the repository at this point in the history
…interactive_icon_button_#115
  • Loading branch information
alanpoon committed Jan 3, 2025
2 parents 84d5e51 + f8863c3 commit 6826070
Show file tree
Hide file tree
Showing 36 changed files with 599 additions and 158 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ unicode-segmentation = "1.11.0"
url = "2.5.0"
emojis = "0.6.1"
bytesize = "1.3.0"

bitflags = "2.6.0"

###################################################################################################
#### Note: we now enable the usage of `rustls-tls` and the `bundled-sqlite` features in the 2 ####
Expand Down
Binary file removed resources/img/favorites.png
Binary file not shown.
Binary file removed resources/img/file_transfer_avatar.png
Binary file not shown.
Binary file removed resources/img/friend_radar.png
Binary file not shown.
Binary file removed resources/img/group_chats.png
Binary file not shown.
Binary file removed resources/img/hero.jpg
Binary file not shown.
Binary file removed resources/img/invite_friends.png
Binary file not shown.
Binary file removed resources/img/keyboard_icon.png
Binary file not shown.
Binary file removed resources/img/mini_programs.png
Binary file not shown.
Binary file removed resources/img/mobile_contacts.png
Binary file not shown.
Binary file removed resources/img/moments.png
Binary file not shown.
Binary file removed resources/img/my-posts.png
Binary file not shown.
Binary file removed resources/img/new_friends.png
Binary file not shown.
Binary file removed resources/img/official_accounts.png
Binary file not shown.
Binary file removed resources/img/people_nearby.png
Binary file not shown.
Binary file removed resources/img/plus.png
Binary file not shown.
Binary file removed resources/img/post1.jpg
Binary file not shown.
Binary file removed resources/img/post2.jpg
Binary file not shown.
Binary file removed resources/img/profile_1.jpg
Binary file not shown.
Binary file removed resources/img/qr_green.png
Binary file not shown.
Binary file removed resources/img/scan.png
Binary file not shown.
Binary file removed resources/img/scan_qr.png
Binary file not shown.
Binary file removed resources/img/search.png
Binary file not shown.
Binary file removed resources/img/settings.png
Binary file not shown.
Binary file removed resources/img/shake.png
Binary file not shown.
Binary file removed resources/img/smiley_face_bw.png
Binary file not shown.
Binary file removed resources/img/sticker-gallery.png
Diff not rendered.
Binary file removed resources/img/tags.png
Diff not rendered.
Binary file removed resources/img/wechat_avatar.png
Diff not rendered.
Binary file removed resources/img/wecom_contacts.png
Diff not rendered.
166 changes: 90 additions & 76 deletions src/home/room_screen.rs

Large diffs are not rendered by default.

258 changes: 254 additions & 4 deletions src/home/rooms_list.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::{collections::HashMap, ops::Deref};
use std::{cmp::Ordering, collections::HashMap, ops::Deref};
use crossbeam_queue::SegQueue;
use imbl::HashSet;
use makepad_widgets::*;
use matrix_sdk::ruma::{events::tag::Tags, MilliSecondsSinceUnixEpoch, OwnedRoomId};

use matrix_sdk::ruma::{events::tag::{TagName, Tags}, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId, OwnedRoomId};
use bitflags::bitflags;
use crate::{app::AppState, sliding_sync::{submit_async_request, MatrixRequest, PaginationDirection}};

use super::room_preview::RoomPreviewAction;
use super::{room_preview::RoomPreviewAction, rooms_sidebar::RoomsViewAction};

/// Whether to pre-paginate visible rooms at least once in order to
/// be able to display the latest message in the room preview,
Expand Down Expand Up @@ -141,6 +142,10 @@ pub struct RoomsListEntry {
pub room_id: OwnedRoomId,
/// The displayable name of this room, if known.
pub room_name: Option<String>,
/// The canonical alias for this room, if any.
pub canonical_alias: Option<OwnedRoomAliasId>,
/// The alternative aliases for this room, if any.
pub alt_aliases: Vec<OwnedRoomAliasId>,
/// The tags associated with this room, if any.
/// This includes things like is_favourite, is_low_priority,
/// whether the room is a server notice room, etc.
Expand Down Expand Up @@ -200,6 +205,188 @@ impl Deref for RoomDisplayFilter {
}
}

bitflags! {
/// The criteria that can be used to filter rooms in the `RoomDisplayFilter`.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct RoomFilterCriteria: u8 {
const RoomId = 0b0000_0001;
const RoomName = 0b0000_0010;
const RoomAlias = 0b0000_0100;
const RoomTags = 0b0000_1000;
const All = Self::RoomId.bits() | Self::RoomName.bits() | Self::RoomAlias.bits() | Self::RoomTags.bits();
}
}

impl Default for RoomFilterCriteria {
fn default() -> Self { RoomFilterCriteria::All }
}

type SortFn = dyn Fn(&RoomsListEntry, &RoomsListEntry) -> Ordering;

/// A builder for creating a `RoomDisplayFilter` with a specific set of filter types and a sorting function.
pub struct RoomDisplayFilterBuilder {
keywords: String,
filter_criteria: RoomFilterCriteria,
sort_fn: Option<Box<SortFn>>,
}
/// ## Example
/// You can create any combination of filters and sorting functions using the `RoomDisplayFilterBuilder`.
/// ```rust,norun
/// let (filter, sort_fn) = RoomDisplayFilterBuilder::new()
/// .set_keywords(keywords)
/// .by_room_id()
/// .by_room_name()
/// .sort_by(|a, b| {
/// let name_a = a.room_name.as_ref().map_or("", |n| n.as_str());
/// let name_b = b.room_name.as_ref().map_or("", |n| n.as_str());
/// name_a.cmp(name_b)
/// })
/// .build();
/// ```
impl RoomDisplayFilterBuilder {
pub fn new() -> Self {
Self {
keywords: String::new(),
filter_criteria: RoomFilterCriteria::default(),
sort_fn: None,
}
}

pub fn set_keywords(mut self, keywords: String) -> Self {
self.keywords = keywords;
self
}

fn set_filter_criteria(mut self, filter_criteria: RoomFilterCriteria) -> Self {
self.filter_criteria = filter_criteria;
self
}

pub fn sort_by<F>(mut self, sort_fn: F) -> Self
where
F: Fn(&RoomsListEntry, &RoomsListEntry) -> Ordering + 'static
{
self.sort_fn = Some(Box::new(sort_fn));
self
}

fn matches_room_id(room: &RoomsListEntry, keywords: &str) -> bool {
room.room_id.to_string().eq_ignore_ascii_case(keywords)
}

fn matches_room_name(room: &RoomsListEntry, keywords: &str) -> bool {
room.room_name
.as_ref()
.map_or(false, |name| name.to_lowercase().contains(keywords))
}

fn matches_room_alias(room: &RoomsListEntry, keywords: &str) -> bool {
room.canonical_alias
.as_ref()
.map_or(false, |alias| alias.as_str().eq_ignore_ascii_case(keywords))
|| room.alt_aliases
.iter()
.any(|alias| alias.as_str().eq_ignore_ascii_case(keywords))
}

fn matches_room_tags(room: &RoomsListEntry, keywords: &str) -> bool {
let search_tags: HashSet<&str> = keywords
.split_whitespace()
.map(|tag| tag.trim_start_matches(':'))
.collect();

fn is_tag_match(search_tag: &str, tag_name: &TagName) -> bool {
match tag_name {
TagName::Favorite => ["favourite", "favorite"].contains(&search_tag),
TagName::LowPriority => ["low_priority", "low-priority", "lowpriority", "lowPriority"].contains(&search_tag),
TagName::ServerNotice => ["server_notice", "server-notice", "servernotice", "serverNotice"].contains(&search_tag),
TagName::User(user_tag) => user_tag.as_ref().eq_ignore_ascii_case(search_tag),
_ => false,
}
}

room.tags.as_ref().map_or(false, |room_tags| {
search_tags.iter().all(|search_tag| {
room_tags.iter().any(|(tag_name, _)| is_tag_match(search_tag, tag_name))
})
})
}

// Check if the keywords have a special prefix that indicates a pre-match filter check.
fn pre_match_filter_check(keywords: &str) -> (RoomFilterCriteria, &str) {
match keywords.chars().next() {
Some('!') => (RoomFilterCriteria::RoomId, keywords),
Some('#') => (RoomFilterCriteria::RoomAlias, keywords),
Some(':') => (RoomFilterCriteria::RoomTags, keywords),
_ => (RoomFilterCriteria::All, keywords),
}
}

fn matches_filter(room: &RoomsListEntry, keywords: &str, filter_criteria: RoomFilterCriteria) -> bool {
if filter_criteria.is_empty() {
return false;
}

let (specific_type, cleaned_keywords) = Self::pre_match_filter_check(keywords);

if specific_type != RoomFilterCriteria::All {
// When using a special prefix, only check that specific type
match specific_type {
RoomFilterCriteria::RoomId if filter_criteria.contains(RoomFilterCriteria::RoomId) => {
Self::matches_room_id(room, cleaned_keywords)
}
RoomFilterCriteria::RoomAlias if filter_criteria.contains(RoomFilterCriteria::RoomAlias) => {
Self::matches_room_alias(room, cleaned_keywords)
}
RoomFilterCriteria::RoomTags if filter_criteria.contains(RoomFilterCriteria::RoomTags) => {
Self::matches_room_tags(room, cleaned_keywords)
}
_ => false
}
} else {
// No special prefix, check all enabled filter types
let mut matches = false;

if filter_criteria.contains(RoomFilterCriteria::RoomId) {
matches |= Self::matches_room_id(room, cleaned_keywords);
}
if filter_criteria.contains(RoomFilterCriteria::RoomName) {
matches |= Self::matches_room_name(room, cleaned_keywords);
}
if filter_criteria.contains(RoomFilterCriteria::RoomAlias) {
matches |= Self::matches_room_alias(room, cleaned_keywords);
}
if filter_criteria.contains(RoomFilterCriteria::RoomTags) {
matches |= Self::matches_room_tags(room, cleaned_keywords);
}

matches
}
}

pub fn build(self) -> (RoomDisplayFilter, Option<Box<SortFn>>) {
let keywords = self.keywords;
let filter_criteria = self.filter_criteria;

let filter = RoomDisplayFilter(Box::new(move |room| {
if keywords.is_empty() || filter_criteria.is_empty() {
return true;
}
let keywords = keywords.trim().to_lowercase();
Self::matches_filter(room, &keywords, self.filter_criteria)
}));

(filter, self.sort_fn)
}

}

impl Default for RoomDisplayFilterBuilder {
fn default() -> Self {
Self::new()
}
}

#[derive(Live, LiveHook, Widget)]
pub struct RoomsList {
#[deref] view: View,
Expand Down Expand Up @@ -233,13 +420,24 @@ pub struct RoomsList {
}

impl RoomsList {
/// Updates the status message to show how many rooms have been loaded.
fn update_status_rooms_count(&mut self) {
self.status = if let Some(max_rooms) = self.max_known_rooms {
format!("Loaded {} of {} total rooms.", self.all_rooms.len(), max_rooms)
} else {
format!("Loaded {} rooms.", self.all_rooms.len())
};
}

/// Updates the status message to show how many rooms are currently displayed
/// that match the current search filter.
fn update_status_matching_rooms(&mut self) {
self.status = match self.displayed_rooms.len() {
0 => "No matching rooms found.".to_string(),
1 => "Found 1 matching room.".to_string(),
n => format!("Found {} matching rooms.", n),
}
}
}

impl Widget for RoomsList {
Expand Down Expand Up @@ -388,6 +586,7 @@ impl Widget for RoomsList {
self.redraw(cx);
}
}
self.widget_match_event(cx, event, scope);
}


Expand Down Expand Up @@ -415,6 +614,7 @@ impl Widget for RoomsList {
list.set_item_range(cx, 0, count + 1);

while let Some(item_id) = list.next_visible_item(cx) {

let mut scope = Scope::empty();

// Draw the room preview for each room in the `displayed_rooms` list.
Expand Down Expand Up @@ -462,3 +662,53 @@ impl Widget for RoomsList {
}

}

impl WidgetMatchEvent for RoomsList {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions, _scope: &mut Scope) {
for action in actions {
if let RoomsViewAction::Search(keywords) = action.as_widget_action().cast() {

if keywords.is_empty() {
// Reset the displayed rooms list to show all rooms.
self.display_filter = RoomDisplayFilter::default();
self.displayed_rooms = self.all_rooms.keys().cloned().collect();
self.update_status_rooms_count();
self.redraw(cx);
return;
}

let (filter, sort_fn) = RoomDisplayFilterBuilder::new()
.set_keywords(keywords.clone())
.set_filter_criteria(RoomFilterCriteria::All)
.build();
self.display_filter = filter;

let displayed_rooms = if let Some(sort_fn) = sort_fn {
let mut filtered_rooms: Vec<_> = self.all_rooms
.iter()
.filter(|(_, room)| (self.display_filter)(room))
.collect();

filtered_rooms.sort_by(|(_, room_a), (_, room_b)| sort_fn(room_a, room_b));

filtered_rooms
.into_iter()
.map(|(room_id, _)| room_id.clone())
.collect()
} else {
self.all_rooms
.iter()
.filter(|(_, room)| (self.display_filter)(room))
.map(|(room_id, _)| room_id.clone())
.collect()
};

// Update the displayed rooms list.
self.displayed_rooms = displayed_rooms;
self.update_status_matching_rooms();
// Redraw the rooms list.
self.redraw(cx);
}
}
}
}
Loading

0 comments on commit 6826070

Please sign in to comment.