1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
use matrix_sdk_ui::timeline::ReactionsByKeyBySender;

use super::ReactionGroup;
use crate::session::model::User;

mod imp {
    use std::cell::{OnceCell, RefCell};

    use indexmap::IndexMap;

    use super::*;

    #[derive(Debug, Default)]
    pub struct ReactionList {
        /// The user of the parent session.
        pub user: OnceCell<User>,

        /// The list of reactions grouped by key.
        pub reactions: RefCell<IndexMap<String, ReactionGroup>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for ReactionList {
        const NAME: &'static str = "ReactionList";
        type Type = super::ReactionList;
        type Interfaces = (gio::ListModel,);
    }

    impl ObjectImpl for ReactionList {}

    impl ListModelImpl for ReactionList {
        fn item_type(&self) -> glib::Type {
            ReactionGroup::static_type()
        }

        fn n_items(&self) -> u32 {
            self.reactions.borrow().len() as u32
        }

        fn item(&self, position: u32) -> Option<glib::Object> {
            let reactions = self.reactions.borrow();

            reactions
                .get_index(position as usize)
                .map(|(_key, reaction_group)| reaction_group.clone().upcast())
        }
    }
}

glib::wrapper! {
    /// List of all `ReactionGroup`s for an event.
    ///
    /// Implements `ListModel`. `ReactionGroup`s are sorted in "insertion order".
    pub struct ReactionList(ObjectSubclass<imp::ReactionList>)
        @implements gio::ListModel;
}

impl ReactionList {
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// The user of the parent session.
    pub fn user(&self) -> &User {
        self.imp().user.get().unwrap()
    }

    /// Set the user of the parent session.
    pub fn set_user(&self, user: User) {
        let _ = self.imp().user.set(user);
    }

    /// Update the reaction list with the given reactions.
    pub fn update(&self, new_reactions: ReactionsByKeyBySender) {
        let reactions = &self.imp().reactions;

        let changed = {
            let old_reactions = reactions.borrow();

            old_reactions.len() != new_reactions.len()
                || new_reactions
                    .keys()
                    .zip(old_reactions.keys())
                    .any(|(new_key, old_key)| new_key != old_key)
        };

        if changed {
            let mut reactions = reactions.borrow_mut();
            let user = self.user();
            let prev_len = reactions.len();
            let new_len = new_reactions.len();

            *reactions = new_reactions
                .iter()
                .map(|(key, reactions)| {
                    let group = ReactionGroup::new(key, user);
                    group.update(reactions);
                    (key.clone(), group)
                })
                .collect();

            // We can't have the borrow active when items_changed is emitted because that
            // will probably cause reads of the reactions field.
            std::mem::drop(reactions);

            self.items_changed(0, prev_len as u32, new_len as u32);
        } else {
            let reactions = reactions.borrow();
            for (reactions, group) in new_reactions.values().zip(reactions.values()) {
                group.update(reactions);
            }
        }
    }

    /// Get a reaction group by its key.
    ///
    /// Returns `None` if no action group was found with this key.
    pub fn reaction_group_by_key(&self, key: &str) -> Option<ReactionGroup> {
        self.imp().reactions.borrow().get(key).cloned()
    }

    /// Remove a reaction group by its key.
    pub fn remove_reaction_group(&self, key: &str) {
        let (pos, ..) = self
            .imp()
            .reactions
            .borrow_mut()
            .shift_remove_full(key)
            .unwrap();
        self.items_changed(pos as u32, 1, 0);
    }

    /// Removes all reactions.
    pub fn clear(&self) {
        let mut reactions = self.imp().reactions.borrow_mut();
        let len = reactions.len();
        reactions.clear();
        std::mem::drop(reactions);
        self.items_changed(0, len as u32, 0);
    }
}

impl Default for ReactionList {
    fn default() -> Self {
        Self::new()
    }
}