matrix_sdk_ui/timeline/event_filter.rs
1// Copyright 2026 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 ruma::events::{
16 AnySyncStateEvent, AnySyncTimelineEvent, SyncStateEvent, TimelineEventType,
17 room::member::{MembershipChange, MembershipState},
18};
19
20/// A timeline filter that in- or excludes events based on their type or
21/// content.
22pub enum TimelineEventFilter {
23 /// Only return items whose event matches any of the conditions in the list.
24 Include(Vec<TimelineEventCondition>),
25 /// Return all items except the ones whose event matches any of the
26 /// conditions in the list
27 Exclude(Vec<TimelineEventCondition>),
28}
29
30impl TimelineEventFilter {
31 /// Filters any incoming `event` using the filter conditions.
32 ///
33 /// # Arguments
34 ///
35 /// * `event` - The event to run the filter on.
36 ///
37 /// # Returns
38 /// `true` if the filter allows the event or `false` otherwise.
39 pub fn filter(&self, event: &AnySyncTimelineEvent) -> bool {
40 match self {
41 Self::Include(conditions) => conditions.iter().any(|c| c.matches(event)),
42 Self::Exclude(conditions) => !conditions.iter().any(|c| c.matches(event)),
43 }
44 }
45}
46
47/// A condition that matches on an event's type or content.
48#[derive(Clone)]
49pub enum TimelineEventCondition {
50 /// The event has the specified event type.
51 EventType(TimelineEventType),
52 /// The event is an `m.room.member` event that represents a membership
53 /// change (join, leave, etc.).
54 MembershipChange(MembershipChangeFilter),
55 /// The event is an `m.room.member` event that represents a profile
56 /// change (displayname or avatar URL).
57 ProfileChange,
58}
59
60/// The membership states that should be included/excluded from the timeline
61/// item filters.
62#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
63#[derive(Clone)]
64pub enum MembershipChangeFilter {
65 /// Include/exclude all membership state events.
66 Any,
67 /// Include/exclude only `join` membership state events.
68 Join,
69 /// Include/exclude only `leave` membership state events.
70 Leave,
71 /// Include/exclude only `invite` membership state events.
72 Invite,
73 /// Include/exclude only `ban` membership state events.
74 Ban,
75 /// Include/exclude only `knock` membership state events.
76 Knock,
77}
78
79impl TimelineEventCondition {
80 /// Evaluate the condition against an event.
81 ///
82 /// # Arguments
83 ///
84 /// * `event` - The event to test the condition against.
85 ///
86 /// # Returns
87 /// `true` if the condition matches or `false` otherwise.
88 fn matches(&self, event: &AnySyncTimelineEvent) -> bool {
89 match self {
90 Self::EventType(event_type) => event.event_type() == *event_type,
91 Self::MembershipChange(filter) => match event {
92 AnySyncTimelineEvent::State(AnySyncStateEvent::RoomMember(
93 SyncStateEvent::Original(ev),
94 )) => {
95 if matches!(ev.membership_change(), MembershipChange::ProfileChanged { .. }) {
96 return false;
97 }
98 match (filter, &ev.content.membership) {
99 (MembershipChangeFilter::Any, _) => {
100 !matches!(ev.membership_change(), MembershipChange::None)
101 }
102 (MembershipChangeFilter::Join, MembershipState::Join) => true,
103 (MembershipChangeFilter::Invite, MembershipState::Invite) => true,
104 (MembershipChangeFilter::Leave, MembershipState::Leave) => true,
105 (MembershipChangeFilter::Knock, MembershipState::Knock) => true,
106 (MembershipChangeFilter::Ban, MembershipState::Ban) => true,
107 _ => false,
108 }
109 }
110 _ => false,
111 },
112 Self::ProfileChange => match event {
113 AnySyncTimelineEvent::State(AnySyncStateEvent::RoomMember(
114 SyncStateEvent::Original(ev),
115 )) => {
116 matches!(ev.membership_change(), MembershipChange::ProfileChanged { .. })
117 }
118 _ => false,
119 },
120 }
121 }
122}