Fetch older messages when scrolling up in the chat message list

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld
2021-08-27 14:58:28 -07:00
co-authored by Nathan Sobo
parent 5262dcd3cb
commit bc63fca8d7
8 changed files with 281 additions and 68 deletions
+149 -56
View File
@@ -37,6 +37,7 @@ pub struct ChannelDetails {
pub struct Channel {
details: ChannelDetails,
messages: SumTree<ChannelMessage>,
loaded_all_messages: bool,
pending_messages: Vec<PendingChannelMessage>,
next_local_message_id: u64,
user_store: Arc<UserStore>,
@@ -70,7 +71,7 @@ pub enum ChannelListEvent {}
#[derive(Clone, Debug, PartialEq)]
pub enum ChannelEvent {
Message {
MessagesAdded {
old_range: Range<usize>,
new_count: usize,
},
@@ -192,31 +193,12 @@ impl Channel {
cx.spawn(|channel, mut cx| {
async move {
let response = rpc.request(proto::JoinChannel { channel_id }).await?;
let unique_user_ids = response
.messages
.iter()
.map(|m| m.sender_id)
.collect::<HashSet<_>>()
.into_iter()
.collect();
user_store.load_users(unique_user_ids).await?;
let mut messages = Vec::with_capacity(response.messages.len());
for message in response.messages {
messages.push(ChannelMessage::from_proto(message, &user_store).await?);
}
let messages = messages_from_proto(response.messages, &user_store).await?;
let loaded_all_messages = response.done;
channel.update(&mut cx, |channel, cx| {
let old_count = channel.messages.summary().count;
let new_count = messages.len();
channel.messages = SumTree::new();
channel.messages.extend(messages, &());
cx.emit(ChannelEvent::Message {
old_range: 0..old_count,
new_count,
});
channel.insert_messages(messages, cx);
channel.loaded_all_messages = loaded_all_messages;
});
Ok(())
@@ -232,6 +214,7 @@ impl Channel {
rpc,
messages: Default::default(),
pending_messages: Default::default(),
loaded_all_messages: false,
next_local_message_id: 0,
_subscription,
}
@@ -264,15 +247,18 @@ impl Channel {
.binary_search_by_key(&local_id, |msg| msg.local_id)
{
let body = this.pending_messages.remove(i).body;
this.insert_message(
ChannelMessage {
id: response.message_id,
timestamp: OffsetDateTime::from_unix_timestamp(
response.timestamp as i64,
)?,
body,
sender,
},
this.insert_messages(
SumTree::from_item(
ChannelMessage {
id: response.message_id,
timestamp: OffsetDateTime::from_unix_timestamp(
response.timestamp as i64,
)?,
body,
sender,
},
&(),
),
cx,
);
}
@@ -286,6 +272,37 @@ impl Channel {
Ok(())
}
pub fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) -> bool {
if !self.loaded_all_messages {
let rpc = self.rpc.clone();
let user_store = self.user_store.clone();
let channel_id = self.details.id;
if let Some(before_message_id) = self.messages.first().map(|message| message.id) {
cx.spawn(|this, mut cx| {
async move {
let response = rpc
.request(proto::GetChannelMessages {
channel_id,
before_message_id,
})
.await?;
let loaded_all_messages = response.done;
let messages = messages_from_proto(response.messages, &user_store).await?;
this.update(&mut cx, |this, cx| {
this.loaded_all_messages = loaded_all_messages;
this.insert_messages(messages, cx);
});
Ok(())
}
.log_err()
})
.detach();
return true;
}
}
false
}
pub fn message_count(&self) -> usize {
self.messages.summary().count
}
@@ -326,7 +343,9 @@ impl Channel {
cx.spawn(|this, mut cx| {
async move {
let message = ChannelMessage::from_proto(message, &user_store).await?;
this.update(&mut cx, |this, cx| this.insert_message(message, cx));
this.update(&mut cx, |this, cx| {
this.insert_messages(SumTree::from_item(message, &()), cx)
});
Ok(())
}
.log_err()
@@ -335,29 +354,51 @@ impl Channel {
Ok(())
}
fn insert_message(&mut self, message: ChannelMessage, cx: &mut ModelContext<Self>) {
let mut old_cursor = self.messages.cursor::<u64, Count>();
let mut new_messages = old_cursor.slice(&message.id, Bias::Left, &());
let start_ix = old_cursor.sum_start().0;
let mut end_ix = start_ix;
if old_cursor.item().map_or(false, |m| m.id == message.id) {
old_cursor.next(&());
end_ix += 1;
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
let mut old_cursor = self.messages.cursor::<u64, Count>();
let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
let start_ix = old_cursor.sum_start().0;
let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
let removed_count = removed_messages.summary().count;
let new_count = messages.summary().count;
let end_ix = start_ix + removed_count;
new_messages.push_tree(messages, &());
new_messages.push_tree(old_cursor.suffix(&()), &());
drop(old_cursor);
self.messages = new_messages;
cx.emit(ChannelEvent::MessagesAdded {
old_range: start_ix..end_ix,
new_count,
});
cx.notify();
}
new_messages.push(message.clone(), &());
new_messages.push_tree(old_cursor.suffix(&()), &());
drop(old_cursor);
self.messages = new_messages;
cx.emit(ChannelEvent::Message {
old_range: start_ix..end_ix,
new_count: 1,
});
cx.notify();
}
}
async fn messages_from_proto(
proto_messages: Vec<proto::ChannelMessage>,
user_store: &UserStore,
) -> Result<SumTree<ChannelMessage>> {
let unique_user_ids = proto_messages
.iter()
.map(|m| m.sender_id)
.collect::<HashSet<_>>()
.into_iter()
.collect();
user_store.load_users(unique_user_ids).await?;
let mut messages = Vec::with_capacity(proto_messages.len());
for message in proto_messages {
messages.push(ChannelMessage::from_proto(message, &user_store).await?);
}
let mut result = SumTree::new();
result.extend(messages, &());
Ok(result)
}
impl From<proto::Channel> for ChannelDetails {
fn from(message: proto::Channel) -> Self {
Self {
@@ -489,9 +530,11 @@ mod tests {
sender_id: 6,
},
],
done: false,
},
)
.await;
// Client requests all users for the received messages
let mut get_users = server.receive::<proto::GetUsers>().await;
get_users.payload.user_ids.sort();
@@ -518,7 +561,7 @@ mod tests {
assert_eq!(
channel.next_event(&cx).await,
ChannelEvent::Message {
ChannelEvent::MessagesAdded {
old_range: 0..0,
new_count: 2,
}
@@ -567,7 +610,7 @@ mod tests {
assert_eq!(
channel.next_event(&cx).await,
ChannelEvent::Message {
ChannelEvent::MessagesAdded {
old_range: 2..2,
new_count: 1,
}
@@ -580,7 +623,57 @@ mod tests {
.collect::<Vec<_>>(),
&[("as-cii".into(), "c".into())]
)
})
});
// Scroll up to view older messages.
channel.update(&mut cx, |channel, cx| {
assert!(channel.load_more_messages(cx));
});
let get_messages = server.receive::<proto::GetChannelMessages>().await;
assert_eq!(get_messages.payload.channel_id, 5);
assert_eq!(get_messages.payload.before_message_id, 10);
server
.respond(
get_messages.receipt(),
proto::GetChannelMessagesResponse {
done: true,
messages: vec![
proto::ChannelMessage {
id: 8,
body: "y".into(),
timestamp: 998,
sender_id: 5,
},
proto::ChannelMessage {
id: 9,
body: "z".into(),
timestamp: 999,
sender_id: 6,
},
],
},
)
.await;
assert_eq!(
channel.next_event(&cx).await,
ChannelEvent::MessagesAdded {
old_range: 0..0,
new_count: 2,
}
);
channel.read_with(&cx, |channel, _| {
assert_eq!(
channel
.messages_in_range(0..2)
.map(|message| (message.sender.github_login.clone(), message.body.clone()))
.collect::<Vec<_>>(),
&[
("nathansobo".into(), "y".into()),
("maxbrunsfeld".into(), "z".into())
]
);
});
}
struct FakeServer {
+16 -1
View File
@@ -22,9 +22,11 @@ pub struct ChatPanel {
pub enum Event {}
action!(Send);
action!(LoadMoreMessages);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ChatPanel::send);
cx.add_action(ChatPanel::load_more_messages);
cx.add_bindings(vec![Binding::new("enter", Send, Some("ChatPanel"))]);
}
@@ -78,6 +80,11 @@ impl ChatPanel {
let subscription = cx.subscribe(&channel, Self::channel_did_change);
self.message_list =
ListState::new(channel.read(cx).message_count(), Orientation::Bottom);
self.message_list.set_scroll_handler(|visible_range, cx| {
if visible_range.start < 5 {
cx.dispatch_action(LoadMoreMessages);
}
});
self.active_channel = Some((channel, subscription));
}
}
@@ -89,7 +96,7 @@ impl ChatPanel {
cx: &mut ViewContext<Self>,
) {
match event {
ChannelEvent::Message {
ChannelEvent::MessagesAdded {
old_range,
new_count,
} => {
@@ -191,6 +198,14 @@ impl ChatPanel {
.log_err();
}
}
fn load_more_messages(&mut self, _: &LoadMoreMessages, cx: &mut ViewContext<Self>) {
if let Some((channel, _)) = self.active_channel.as_ref() {
channel.update(cx, |channel, cx| {
channel.load_more_messages(cx);
})
}
}
}
impl Entity for ChatPanel {