diff --git a/Cargo.lock b/Cargo.lock index b41629ecf..ab59bcd7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2944,6 +2944,7 @@ dependencies = [ "futures-util", "imbl", "indexmap", + "itertools", "matrix-sdk", "matrix-sdk-base", "matrix-sdk-crypto", diff --git a/Cargo.toml b/Cargo.toml index aec8cd99f..f7bcffc0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ futures-core = "0.3.28" futures-executor = "0.3.21" futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] } http = "0.2.6" +itertools = "0.10.5" ruma = { git = "https://github.com/ruma/ruma", rev = "cf32036df4c9daca736dcd7f0d9d65debcf9897f", features = ["client-api-c", "compat-user-id"] } ruma-common = { git = "https://github.com/ruma/ruma", rev = "cf32036df4c9daca736dcd7f0d9d65debcf9897f" } once_cell = "1.16.0" diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index cc155eb60..5b06879ad 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -44,7 +44,7 @@ futures-util = { workspace = true } hkdf = { version = "0.12.3", optional = true } hmac = "0.12.1" http = { workspace = true, optional = true } # feature = testing only -itertools = "0.10.5" +itertools = { workspace = true } matrix-sdk-qrcode = { version = "0.4.0", path = "../matrix-sdk-qrcode", optional = true } matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" } pbkdf2 = { version = "0.11.0", default-features = false } diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index 6ee0eeeec..2fb8578bd 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -30,6 +30,7 @@ futures-core = { workspace = true } futures-util = { workspace = true } imbl = { version = "2.0.0", features = ["serde"] } indexmap = "1.9.1" +itertools = { workspace = true } matrix-sdk = { version = "0.6.2", path = "../matrix-sdk", default-features = false } matrix-sdk-base = { version = "0.6.1", path = "../matrix-sdk-base" } matrix-sdk-crypto = { version = "0.6.0", path = "../matrix-sdk-crypto" } diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/content.rs b/crates/matrix-sdk-ui/src/timeline/event_item/content.rs index 921c92d33..4acdbc842 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/content.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/content.rs @@ -16,6 +16,7 @@ use std::{fmt, ops::Deref, sync::Arc}; use imbl::{vector, Vector}; use indexmap::IndexMap; +use itertools::Itertools; use matrix_sdk::{deserialized_responses::TimelineEvent, Result}; use ruma::{ assign, @@ -398,9 +399,9 @@ type ReactionGroupInner = IndexMap<(Option, Option impl Iterator { - self.values().map(AsRef::as_ref) + self.values().unique().map(AsRef::as_ref) } } diff --git a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs index 6da4bb951..7079dabf6 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs @@ -53,6 +53,7 @@ mod edit; #[cfg(feature = "e2e-encryption")] mod encryption; mod invalid; +mod reaction_group; mod read_receipts; mod redaction; mod virt; diff --git a/crates/matrix-sdk-ui/src/timeline/tests/reaction_group.rs b/crates/matrix-sdk-ui/src/timeline/tests/reaction_group.rs new file mode 100644 index 000000000..98c4077d7 --- /dev/null +++ b/crates/matrix-sdk-ui/src/timeline/tests/reaction_group.rs @@ -0,0 +1,41 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use ruma::{server_name, EventId, UserId}; + +use crate::timeline::{ + tests::{ALICE, BOB}, + ReactionGroup, +}; + +/// The Matrix spec does not allow duplicate annotations to be created but it +/// is still possible for duplicates to be received over federation. And in +/// that case, clients are expected to treat duplicates as a single annotation. +#[test] +fn senders_are_deduplicated() { + let group = { + let mut group = ReactionGroup::default(); + insert(&mut group, &ALICE, 3); + insert(&mut group, &BOB, 2); + group + }; + + assert_eq!(group.senders().collect::>(), vec![&ALICE.to_owned(), &BOB.to_owned()]); +} + +fn insert(group: &mut ReactionGroup, sender: &UserId, count: u64) { + for _ in 0..count { + let event_id = EventId::new(server_name!("dummy.server")); + group.0.insert((None, Some(event_id)), sender.to_owned()); + } +}