matrix_sdk_ui/room_list_service/filters/
category.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
15use super::{super::Room, Filter};
16
17/// An enum to represent whether a room is about “people” (strictly 2 users) or
18/// “group” (1 or more than 2 users).
19///
20/// Ideally, we would only want to rely on the
21/// [`matrix_sdk::BaseRoom::is_direct`] method, but the rules are a little bit
22/// different for this high-level UI API.
23///
24/// This is implemented this way so that it's impossible to filter by “group”
25/// and by “people” at the same time: these criteria are mutually
26/// exclusive by design per filter.
27#[derive(Copy, Clone, PartialEq)]
28pub enum RoomCategory {
29    Group,
30    People,
31}
32
33type DirectTargetsLength = usize;
34
35struct CategoryRoomMatcher<F>
36where
37    F: Fn(&Room) -> Option<DirectTargetsLength>,
38{
39    /// _Direct targets_ mean the number of users in a direct room, except us.
40    /// So if it returns 1, it means there are 2 users in the direct room.
41    number_of_direct_targets: F,
42}
43
44impl<F> CategoryRoomMatcher<F>
45where
46    F: Fn(&Room) -> Option<DirectTargetsLength>,
47{
48    fn matches(&self, room: &Room, expected_kind: RoomCategory) -> bool {
49        let kind = match (self.number_of_direct_targets)(room) {
50            // If 1, we are sure it's a direct room between two users. It's the strict
51            // definition of the `People` category, all good.
52            Some(1) => RoomCategory::People,
53
54            // If smaller than 1, we are not sure it's a direct room, it's then a `Group`.
55            // If greater than 1, we are sure it's a direct room but not between
56            // two users, so it's a `Group` based on our expectation.
57            Some(_) => RoomCategory::Group,
58
59            // Don't know.
60            None => return false,
61        };
62
63        kind == expected_kind
64    }
65}
66
67/// Create a new filter that will accept all rooms that fit in the
68/// `expected_category`. The category is defined by [`RoomCategory`], see this
69/// type to learn more.
70pub fn new_filter(expected_category: RoomCategory) -> impl Filter {
71    let matcher = CategoryRoomMatcher {
72        number_of_direct_targets: move |room| Some(room.direct_targets_length()),
73    };
74
75    move |room| -> bool { matcher.matches(room, expected_category) }
76}
77
78#[cfg(test)]
79mod tests {
80    use std::ops::Not;
81
82    use matrix_sdk::test_utils::logged_in_client_with_server;
83    use matrix_sdk_test::async_test;
84    use ruma::room_id;
85
86    use super::{super::new_rooms, *};
87
88    #[async_test]
89    async fn test_kind_is_group() {
90        let (client, server) = logged_in_client_with_server().await;
91        let [room] = new_rooms([room_id!("!a:b.c")], &client, &server).await;
92
93        let matcher = CategoryRoomMatcher { number_of_direct_targets: |_| Some(42) };
94
95        // Expect `People`.
96        {
97            let expected_kind = RoomCategory::People;
98
99            assert!(matcher.matches(&room, expected_kind).not());
100        }
101
102        // Expect `Group`.
103        {
104            let expected_kind = RoomCategory::Group;
105
106            assert!(matcher.matches(&room, expected_kind));
107        }
108    }
109
110    #[async_test]
111    async fn test_kind_is_people() {
112        let (client, server) = logged_in_client_with_server().await;
113        let [room] = new_rooms([room_id!("!a:b.c")], &client, &server).await;
114
115        let matcher = CategoryRoomMatcher { number_of_direct_targets: |_| Some(1) };
116
117        // Expect `People`.
118        {
119            let expected_kind = RoomCategory::People;
120
121            assert!(matcher.matches(&room, expected_kind));
122        }
123
124        // Expect `Group`.
125        {
126            let expected_kind = RoomCategory::Group;
127
128            assert!(matcher.matches(&room, expected_kind).not());
129        }
130    }
131
132    #[async_test]
133    async fn test_room_kind_cannot_be_found() {
134        let (client, server) = logged_in_client_with_server().await;
135        let [room] = new_rooms([room_id!("!a:b.c")], &client, &server).await;
136
137        let matcher = CategoryRoomMatcher { number_of_direct_targets: |_| None };
138
139        assert!(matcher.matches(&room, RoomCategory::Group).not());
140    }
141}