matrix_sdk_ui/room_list_service/filters/
mod.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A collection of room filters.
16//!
17//! The room list can provide an access to the rooms per list, like with
18//! [`super::RoomList::entries_with_dynamic_adapters`]. The provided collection
19//! of rooms can be filtered with these filters. A classical usage would be the
20//! following:
21//!
22//! ```rust
23//! use matrix_sdk_ui::room_list_service::{
24//!     RoomListDynamicEntriesController, filters,
25//! };
26//!
27//! fn configure_room_list(
28//!     entries_controller: &RoomListDynamicEntriesController,
29//! ) {
30//!     // _All_ non-left rooms
31//!     // _and_ that fall in the “People” category,
32//!     // _and_ that are marked as favourite,
33//!     // _and_ that are _not_ unread.
34//!     entries_controller.set_filter(Box::new(
35//!         // All
36//!         filters::new_filter_all(vec![
37//!             // Non-left
38//!             Box::new(filters::new_filter_non_left()),
39//!             // People
40//!             Box::new(filters::new_filter_category(
41//!                 filters::RoomCategory::People,
42//!             )),
43//!             // Favourite
44//!             Box::new(filters::new_filter_favourite()),
45//!             // Not Unread
46//!             Box::new(filters::new_filter_not(Box::new(
47//!                 filters::new_filter_unread(),
48//!             ))),
49//!         ]),
50//!     ));
51//! }
52//! ```
53
54mod all;
55mod any;
56mod category;
57mod deduplicate_versions;
58mod favourite;
59mod fuzzy_match_room_name;
60mod invite;
61mod joined;
62mod low_priority;
63mod non_left;
64mod none;
65mod normalized_match_room_name;
66mod not;
67mod space;
68mod unread;
69
70pub use all::new_filter as new_filter_all;
71pub use any::new_filter as new_filter_any;
72pub use category::{RoomCategory, new_filter as new_filter_category};
73pub use deduplicate_versions::new_filter as new_filter_deduplicate_versions;
74pub use favourite::new_filter as new_filter_favourite;
75pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name;
76pub use invite::new_filter as new_filter_invite;
77pub use joined::new_filter as new_filter_joined;
78pub use low_priority::new_filter as new_filter_low_priority;
79#[cfg(test)]
80use matrix_sdk::Client;
81#[cfg(test)]
82use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder};
83pub use non_left::new_filter as new_filter_non_left;
84pub use none::new_filter as new_filter_none;
85pub use normalized_match_room_name::new_filter as new_filter_normalized_match_room_name;
86pub use not::new_filter as new_filter_not;
87#[cfg(test)]
88use ruma::RoomId;
89pub use space::new_filter as new_filter_space;
90use unicode_normalization::{UnicodeNormalization, char::is_combining_mark};
91pub use unread::new_filter as new_filter_unread;
92#[cfg(test)]
93use wiremock::{
94    Mock, MockServer, ResponseTemplate,
95    matchers::{header, method, path},
96};
97
98use super::RoomListItem;
99
100/// A trait “alias” that represents a _filter_.
101///
102/// A filter is simply a function that receives a `&Room` and returns a `bool`.
103pub trait Filter: Fn(&RoomListItem) -> bool {}
104
105impl<F> Filter for F where F: Fn(&RoomListItem) -> bool {}
106
107/// Type alias for a boxed filter function.
108#[cfg(not(target_family = "wasm"))]
109pub type BoxedFilterFn = Box<dyn Filter + Send + Sync>;
110#[cfg(target_family = "wasm")]
111pub type BoxedFilterFn = Box<dyn Filter>;
112
113/// Normalize a string, i.e. decompose it into NFD (Normalization Form D, i.e. a
114/// canonical decomposition, see http://www.unicode.org/reports/tr15/) and
115/// filter out the combining marks.
116fn normalize_string(str: &str) -> String {
117    str.nfd().filter(|c| !is_combining_mark(*c)).collect::<String>()
118}
119
120#[cfg(test)]
121pub(super) async fn new_rooms<const N: usize>(
122    room_ids: [&RoomId; N],
123    client: &Client,
124    server: &MockServer,
125) -> [RoomListItem; N] {
126    let mut response_builder = SyncResponseBuilder::default();
127
128    for room_id in room_ids {
129        response_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
130    }
131
132    let json_response = response_builder.build_json_sync_response();
133
134    let _scope = Mock::given(method("GET"))
135        .and(path("/_matrix/client/r0/sync"))
136        .and(header("authorization", "Bearer 1234"))
137        .respond_with(ResponseTemplate::new(200).set_body_json(json_response))
138        .mount_as_scoped(server)
139        .await;
140
141    let _response = client.sync_once(Default::default()).await.unwrap();
142
143    room_ids.map(|room_id| client.get_room(room_id).unwrap().into())
144}
145
146#[cfg(test)]
147mod tests {
148    use super::normalize_string;
149
150    #[test]
151    fn test_normalize_string() {
152        assert_eq!(&normalize_string("abc"), "abc");
153        assert_eq!(&normalize_string("Ștefan Été"), "Stefan Ete");
154        assert_eq!(&normalize_string("Ç ṩ ḋ Å"), "C s d A");
155        assert_eq!(&normalize_string("هند"), "هند");
156    }
157}