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//!     filters, RoomListDynamicEntriesController,
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 non_left;
63mod none;
64mod normalized_match_room_name;
65mod not;
66mod unread;
67
68pub use all::new_filter as new_filter_all;
69pub use any::new_filter as new_filter_any;
70pub use category::{new_filter as new_filter_category, RoomCategory};
71pub use deduplicate_versions::new_filter as new_filter_deduplicate_versions;
72pub use favourite::new_filter as new_filter_favourite;
73pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name;
74pub use invite::new_filter as new_filter_invite;
75pub use joined::new_filter as new_filter_joined;
76#[cfg(test)]
77use matrix_sdk::Client;
78use matrix_sdk::Room;
79#[cfg(test)]
80use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder};
81pub use non_left::new_filter as new_filter_non_left;
82pub use none::new_filter as new_filter_none;
83pub use normalized_match_room_name::new_filter as new_filter_normalized_match_room_name;
84pub use not::new_filter as new_filter_not;
85#[cfg(test)]
86use ruma::RoomId;
87use unicode_normalization::{char::is_combining_mark, UnicodeNormalization};
88pub use unread::new_filter as new_filter_unread;
89#[cfg(test)]
90use wiremock::{
91    matchers::{header, method, path},
92    Mock, MockServer, ResponseTemplate,
93};
94
95/// A trait “alias” that represents a _filter_.
96///
97/// A filter is simply a function that receives a `&Room` and returns a `bool`.
98pub trait Filter: Fn(&Room) -> bool {}
99
100impl<F> Filter for F where F: Fn(&Room) -> bool {}
101
102/// Type alias for a boxed filter function.
103#[cfg(not(target_family = "wasm"))]
104pub type BoxedFilterFn = Box<dyn Filter + Send + Sync>;
105#[cfg(target_family = "wasm")]
106pub type BoxedFilterFn = Box<dyn Filter>;
107
108/// Normalize a string, i.e. decompose it into NFD (Normalization Form D, i.e. a
109/// canonical decomposition, see http://www.unicode.org/reports/tr15/) and
110/// filter out the combining marks.
111fn normalize_string(str: &str) -> String {
112    str.nfd().filter(|c| !is_combining_mark(*c)).collect::<String>()
113}
114
115#[cfg(test)]
116pub(super) async fn new_rooms<const N: usize>(
117    room_ids: [&RoomId; N],
118    client: &Client,
119    server: &MockServer,
120) -> [Room; N] {
121    let mut response_builder = SyncResponseBuilder::default();
122
123    for room_id in room_ids {
124        response_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
125    }
126
127    let json_response = response_builder.build_json_sync_response();
128
129    let _scope = Mock::given(method("GET"))
130        .and(path("/_matrix/client/r0/sync"))
131        .and(header("authorization", "Bearer 1234"))
132        .respond_with(ResponseTemplate::new(200).set_body_json(json_response))
133        .mount_as_scoped(server)
134        .await;
135
136    let _response = client.sync_once(Default::default()).await.unwrap();
137
138    room_ids.map(|room_id| client.get_room(room_id).unwrap())
139}
140
141#[cfg(test)]
142mod tests {
143    use super::normalize_string;
144
145    #[test]
146    fn test_normalize_string() {
147        assert_eq!(&normalize_string("abc"), "abc");
148        assert_eq!(&normalize_string("Ștefan Été"), "Stefan Ete");
149        assert_eq!(&normalize_string("Ç ṩ ḋ Å"), "C s d A");
150        assert_eq!(&normalize_string("هند"), "هند");
151    }
152}