tilo_app/framework/
people.rs1use std::cell::RefCell;
12use std::collections::HashMap;
13use std::rc::Rc;
14
15use ui_core::LocalState;
16use ui_core::permissions::UserId;
17use ui_core::presence::PresenceCtx;
18
19#[derive(Clone)]
22pub struct People {
23 roster: Rc<RefCell<Roster>>,
24 revision: LocalState<u64>,
25 me: Option<Rc<str>>,
27}
28
29#[derive(Default, PartialEq)]
30struct Roster {
31 names: HashMap<String, String>,
32 agents: Vec<String>,
33 own: Option<String>,
35}
36
37const YOU: &str = "You";
40
41impl People {
42 pub(crate) fn new(me: Option<&str>) -> Self {
43 Self {
44 roster: Rc::default(),
45 revision: LocalState::new(0),
46 me: me.map(Rc::from),
47 }
48 }
49
50 pub fn name(&self, id: &str) -> String {
55 let roster = self.roster.borrow();
56 if let Some(name) = roster.names.get(id) {
57 return if roster.agents.iter().any(|agent| agent == id) {
58 ui_core::history_source::agent_display_label(name)
59 } else {
60 name.clone()
61 };
62 }
63 if self.me.as_deref() == Some(id) {
64 return roster.own.clone().unwrap_or_else(|| YOU.to_owned());
65 }
66 id.to_owned()
67 }
68
69 pub fn is_me(&self, id: &str) -> bool {
71 self.me.as_deref() == Some(id)
72 }
73
74 pub fn revision(&self) -> LocalState<u64> {
77 self.revision.clone()
78 }
79
80 pub(crate) fn follow(&self, presence: &PresenceCtx) -> bool {
82 let next = Roster {
83 names: presence
84 .names
85 .iter()
86 .map(|(UserId(id), name)| (id.clone(), name.clone()))
87 .collect(),
88 agents: presence
89 .names
90 .keys()
91 .filter(|user| presence.is_agent(user))
92 .map(|UserId(id)| id.clone())
93 .collect(),
94 own: presence.self_name().map(str::to_owned),
95 };
96 if *self.roster.borrow() == next {
97 return false;
98 }
99 *self.roster.borrow_mut() = next;
100 self.revision.set(self.revision.get().wrapping_add(1));
101 true
102 }
103
104 #[cfg(any(test, feature = "test-support"))]
107 #[must_use]
108 pub fn named(me: Option<&str>, names: &[(&str, &str)]) -> Self {
109 let people = Self::new(me);
110 people.roster.borrow_mut().names = names
111 .iter()
112 .map(|(id, name)| ((*id).to_owned(), (*name).to_owned()))
113 .collect();
114 people
115 }
116}
117
118impl std::fmt::Debug for People {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 f.debug_struct("People")
121 .field("me", &self.me)
122 .field("known", &self.roster.borrow().names.len())
123 .field("revision", &self.revision.get())
124 .finish()
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn an_id_reads_as_the_rosters_name_you_or_itself() {
134 let people = People::new(Some("me-id"));
135 assert_eq!(people.name("me-id"), "You");
136 assert_eq!(
137 people.name("Alice"),
138 "Alice",
139 "an unknown id reads as itself"
140 );
141
142 let mut presence = PresenceCtx::default();
143 presence.set_display_name(UserId("peer-id".into()), "Bob");
144 presence.set_self_participant(UserId("me-id".into()), "Ada");
145 assert!(people.follow(&presence));
146 assert!(
147 !people.follow(&presence),
148 "an unchanged roster changes nothing"
149 );
150 assert_eq!(people.name("peer-id"), "Bob");
151 assert_eq!(people.name("me-id"), "Ada");
152 assert!(people.is_me("me-id"));
153 assert_eq!(people.revision().get(), 1);
154 }
155}