egui_charts/services/profiles.rs
1//! Profiles of other users and the follow relations the host keeps.
2
3use super::Events;
4use crate::data::{MaybeSend, Reply};
5
6/// The host's identifier of a profile.
7#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
8pub struct ProfileId(
9 /// The host's own identifier, unique among its profiles.
10 pub String,
11);
12
13/// What the host knows about a user the signed-in user can follow.
14#[derive(Clone, Debug, PartialEq)]
15#[non_exhaustive]
16pub struct Profile {
17 /// The host's identifier.
18 pub id: ProfileId,
19 /// Display name.
20 pub name: String,
21 /// Short description the user wrote. May be empty.
22 pub bio: String,
23 /// URI of the user's picture, for egui's installed image loaders.
24 pub avatar: Option<String>,
25 /// How many users follow this one, when the host reports it.
26 pub followers: Option<u64>,
27 /// Whether the signed-in user follows this user, as the host last reported.
28 pub followed: bool,
29}
30
31impl Profile {
32 /// A profile that is not followed, without a bio, picture or follower count.
33 pub fn new(id: ProfileId, name: impl Into<String>) -> Self {
34 Self {
35 id,
36 name: name.into(),
37 bio: String::new(),
38 avatar: None,
39 followers: None,
40 followed: false,
41 }
42 }
43}
44
45/// The host's profiles and follow relations. The host keeps the account, decides who may follow
46/// whom and what a profile shows; the library shows what the host pushes and sends the user's
47/// follow and unfollow requests through [`follow`](Self::follow). It never changes
48/// [`Profile::followed`] itself.
49///
50/// Methods are called on the UI thread and must return at once; answer and push from any
51/// thread, runtime or browser task.
52///
53/// ```
54/// use std::sync::Mutex;
55///
56/// use egui_charts::data::Reply;
57/// use egui_charts::services::{Events, Profile, ProfileEvent, ProfileId, Profiles};
58///
59/// /// One profile that accepts every follow request. A real host asks its server.
60/// #[derive(Default)]
61/// struct Local(Mutex<Option<Events<ProfileEvent>>>);
62///
63/// impl Profiles for Local {
64/// fn watch(&self, events: Events<ProfileEvent>) {
65/// events.push(ProfileEvent::Upsert(Profile::new(ProfileId("1".into()), "Ada")));
66/// *self.0.lock().unwrap() = Some(events);
67/// }
68///
69/// fn follow(&self, id: ProfileId, follow: bool, reply: Reply<()>) {
70/// if let Some(events) = &*self.0.lock().unwrap() {
71/// let mut profile = Profile::new(id, "Ada");
72/// profile.followed = follow;
73/// events.push(ProfileEvent::Upsert(profile));
74/// }
75/// reply.send(Ok(()));
76/// }
77/// }
78/// ```
79pub trait Profiles: MaybeSend + 'static {
80 /// Pushes the profiles the user can see, then every change until `events` is inactive. Called
81 /// when the directory starts and again after a lagged stream, each time with a new stream that
82 /// starts from no profiles. The stream takes at most 10,000 pushes between two frames.
83 fn watch(&self, events: Events<ProfileEvent>);
84
85 /// Follows `id` when `follow` is true, otherwise unfollows it. Push the profile with its new
86 /// [`Profile::followed`] as [`ProfileEvent::Upsert`] first, then answer `Ok` once the relation
87 /// changed, or with a [`FeedError`](crate::data::FeedError) the user can read if the host
88 /// refused. Authorization belongs to the host: refuse a request the signed-in user may not
89 /// make.
90 fn follow(&self, id: ProfileId, follow: bool, reply: Reply<()>);
91}
92
93/// A change to the host's profiles, pushed through the stream [`Profiles::watch`] received.
94#[derive(Clone, Debug, PartialEq)]
95#[non_exhaustive]
96pub enum ProfileEvent {
97 /// Adds the profile, or replaces the one with its id.
98 Upsert(Profile),
99 /// Removes the profile with this id, if any.
100 Remove(ProfileId),
101 /// Removes every profile, so a host that reconnects can push them again.
102 Clear,
103}