Start work on notification panel
This commit is contained in:
parent
50cf25ae97
commit
d1756b621f
24 changed files with 1021 additions and 241 deletions
42
crates/notifications/Cargo.toml
Normal file
42
crates/notifications/Cargo.toml
Normal file
|
@ -0,0 +1,42 @@
|
|||
[package]
|
||||
name = "notifications"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/notification_store.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"channel/test-support",
|
||||
"collections/test-support",
|
||||
"gpui/test-support",
|
||||
"rpc/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
channel = { path = "../channel" }
|
||||
client = { path = "../client" }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
db = { path = "../db" }
|
||||
feature_flags = { path = "../feature_flags" }
|
||||
gpui = { path = "../gpui" }
|
||||
rpc = { path = "../rpc" }
|
||||
settings = { path = "../settings" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
text = { path = "../text" }
|
||||
util = { path = "../util" }
|
||||
|
||||
anyhow.workspace = true
|
||||
time.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
256
crates/notifications/src/notification_store.rs
Normal file
256
crates/notifications/src/notification_store.rs
Normal file
|
@ -0,0 +1,256 @@
|
|||
use anyhow::Result;
|
||||
use channel::{ChannelMessage, ChannelMessageId, ChannelStore};
|
||||
use client::{Client, UserStore};
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle};
|
||||
use rpc::{proto, Notification, NotificationKind, TypedEnvelope};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
|
||||
let notification_store = cx.add_model(|cx| NotificationStore::new(client, user_store, cx));
|
||||
cx.set_global(notification_store);
|
||||
}
|
||||
|
||||
pub struct NotificationStore {
|
||||
_client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
channel_messages: HashMap<u64, ChannelMessage>,
|
||||
channel_store: ModelHandle<ChannelStore>,
|
||||
notifications: SumTree<NotificationEntry>,
|
||||
_subscriptions: Vec<client::Subscription>,
|
||||
}
|
||||
|
||||
pub enum NotificationEvent {
|
||||
NotificationsUpdated {
|
||||
old_range: Range<usize>,
|
||||
new_count: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct NotificationEntry {
|
||||
pub id: u64,
|
||||
pub notification: Notification,
|
||||
pub timestamp: OffsetDateTime,
|
||||
pub is_read: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NotificationSummary {
|
||||
max_id: u64,
|
||||
count: usize,
|
||||
unread_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct Count(usize);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct UnreadCount(usize);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct NotificationId(u64);
|
||||
|
||||
impl NotificationStore {
|
||||
pub fn global(cx: &AppContext) -> ModelHandle<Self> {
|
||||
cx.global::<ModelHandle<Self>>().clone()
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
channel_store: ChannelStore::global(cx),
|
||||
notifications: Default::default(),
|
||||
channel_messages: Default::default(),
|
||||
_subscriptions: vec![
|
||||
client.add_message_handler(cx.handle(), Self::handle_add_notifications)
|
||||
],
|
||||
user_store,
|
||||
_client: client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notification_count(&self) -> usize {
|
||||
self.notifications.summary().count
|
||||
}
|
||||
|
||||
pub fn unread_notification_count(&self) -> usize {
|
||||
self.notifications.summary().unread_count
|
||||
}
|
||||
|
||||
pub fn channel_message_for_id(&self, id: u64) -> Option<&ChannelMessage> {
|
||||
self.channel_messages.get(&id)
|
||||
}
|
||||
|
||||
pub fn notification_at(&self, ix: usize) -> Option<&NotificationEntry> {
|
||||
let mut cursor = self.notifications.cursor::<Count>();
|
||||
cursor.seek(&Count(ix), Bias::Right, &());
|
||||
cursor.item()
|
||||
}
|
||||
|
||||
async fn handle_add_notifications(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::AddNotifications>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let mut user_ids = Vec::new();
|
||||
let mut message_ids = Vec::new();
|
||||
|
||||
let notifications = envelope
|
||||
.payload
|
||||
.notifications
|
||||
.into_iter()
|
||||
.filter_map(|message| {
|
||||
Some(NotificationEntry {
|
||||
id: message.id,
|
||||
is_read: message.is_read,
|
||||
timestamp: OffsetDateTime::from_unix_timestamp(message.timestamp as i64)
|
||||
.ok()?,
|
||||
notification: Notification::from_parts(
|
||||
NotificationKind::from_i32(message.kind as i32)?,
|
||||
[
|
||||
message.entity_id_1,
|
||||
message.entity_id_2,
|
||||
message.entity_id_3,
|
||||
],
|
||||
)?,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if notifications.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for entry in ¬ifications {
|
||||
match entry.notification {
|
||||
Notification::ChannelInvitation { inviter_id, .. } => {
|
||||
user_ids.push(inviter_id);
|
||||
}
|
||||
Notification::ContactRequest { requester_id } => {
|
||||
user_ids.push(requester_id);
|
||||
}
|
||||
Notification::ContactRequestAccepted { contact_id } => {
|
||||
user_ids.push(contact_id);
|
||||
}
|
||||
Notification::ChannelMessageMention {
|
||||
sender_id,
|
||||
message_id,
|
||||
..
|
||||
} => {
|
||||
user_ids.push(sender_id);
|
||||
message_ids.push(message_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (user_store, channel_store) = this.read_with(&cx, |this, _| {
|
||||
(this.user_store.clone(), this.channel_store.clone())
|
||||
});
|
||||
|
||||
user_store
|
||||
.update(&mut cx, |store, cx| store.get_users(user_ids, cx))
|
||||
.await?;
|
||||
let messages = channel_store
|
||||
.update(&mut cx, |store, cx| {
|
||||
store.fetch_channel_messages(message_ids, cx)
|
||||
})
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.channel_messages
|
||||
.extend(messages.into_iter().filter_map(|message| {
|
||||
if let ChannelMessageId::Saved(id) = message.id {
|
||||
Some((id, message))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
|
||||
let mut cursor = this.notifications.cursor::<(NotificationId, Count)>();
|
||||
let mut new_notifications = SumTree::new();
|
||||
let mut old_range = 0..0;
|
||||
for (i, notification) in notifications.into_iter().enumerate() {
|
||||
new_notifications.append(
|
||||
cursor.slice(&NotificationId(notification.id), Bias::Left, &()),
|
||||
&(),
|
||||
);
|
||||
|
||||
if i == 0 {
|
||||
old_range.start = cursor.start().1 .0;
|
||||
}
|
||||
|
||||
if cursor
|
||||
.item()
|
||||
.map_or(true, |existing| existing.id != notification.id)
|
||||
{
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
new_notifications.push(notification, &());
|
||||
}
|
||||
|
||||
old_range.end = cursor.start().1 .0;
|
||||
let new_count = new_notifications.summary().count;
|
||||
new_notifications.append(cursor.suffix(&()), &());
|
||||
drop(cursor);
|
||||
|
||||
this.notifications = new_notifications;
|
||||
cx.emit(NotificationEvent::NotificationsUpdated {
|
||||
old_range,
|
||||
new_count,
|
||||
});
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for NotificationStore {
|
||||
type Event = NotificationEvent;
|
||||
}
|
||||
|
||||
impl sum_tree::Item for NotificationEntry {
|
||||
type Summary = NotificationSummary;
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
NotificationSummary {
|
||||
max_id: self.id,
|
||||
count: 1,
|
||||
unread_count: if self.is_read { 0 } else { 1 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for NotificationSummary {
|
||||
type Context = ();
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
self.max_id = self.max_id.max(summary.max_id);
|
||||
self.count += summary.count;
|
||||
self.unread_count += summary.unread_count;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, NotificationSummary> for NotificationId {
|
||||
fn add_summary(&mut self, summary: &NotificationSummary, _: &()) {
|
||||
debug_assert!(summary.max_id > self.0);
|
||||
self.0 = summary.max_id;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, NotificationSummary> for Count {
|
||||
fn add_summary(&mut self, summary: &NotificationSummary, _: &()) {
|
||||
self.0 += summary.count;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, NotificationSummary> for UnreadCount {
|
||||
fn add_summary(&mut self, summary: &NotificationSummary, _: &()) {
|
||||
self.0 += summary.unread_count;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue