Skip to content

Commit 398e340

Browse files
committed
refactor(chat-history-model): Use sections as day dividers
Using sections simplifies our code a lot. It still needs changes applied to the reversed list patch, otherwise sections are shifted forward by one position.
1 parent bd2b8cc commit 398e340

File tree

6 files changed

+117
-289
lines changed

6 files changed

+117
-289
lines changed

src/model/chat_history_item.rs

Lines changed: 0 additions & 92 deletions
This file was deleted.

src/model/chat_history_model.rs

Lines changed: 51 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use std::cell::Cell;
22
use std::cell::RefCell;
3-
use std::cmp::Ordering;
43
use std::collections::VecDeque;
54

65
use gio::prelude::*;
7-
use gio::subclass::prelude::*;
86
use glib::clone;
97
use gtk::gio;
108
use gtk::glib;
9+
use gtk::subclass::prelude::*;
1110
use once_cell::sync::Lazy;
1211
use thiserror::Error;
1312

@@ -28,14 +27,14 @@ mod imp {
2827
pub(crate) struct ChatHistoryModel {
2928
pub(super) chat: glib::WeakRef<model::Chat>,
3029
pub(super) is_loading: Cell<bool>,
31-
pub(super) list: RefCell<VecDeque<model::ChatHistoryItem>>,
30+
pub(super) list: RefCell<VecDeque<model::Message>>,
3231
}
3332

3433
#[glib::object_subclass]
3534
impl ObjectSubclass for ChatHistoryModel {
3635
const NAME: &'static str = "ChatHistoryModel";
3736
type Type = super::ChatHistoryModel;
38-
type Interfaces = (gio::ListModel,);
37+
type Interfaces = (gio::ListModel, gtk::SectionModel);
3938
}
4039

4140
impl ObjectImpl for ChatHistoryModel {
@@ -60,7 +59,7 @@ mod imp {
6059

6160
impl ListModelImpl for ChatHistoryModel {
6261
fn item_type(&self) -> glib::Type {
63-
model::ChatHistoryItem::static_type()
62+
model::Message::static_type()
6463
}
6564

6665
fn n_items(&self) -> u32 {
@@ -75,6 +74,44 @@ mod imp {
7574
.cloned()
7675
}
7776
}
77+
78+
impl SectionModelImpl for ChatHistoryModel {
79+
fn section(&self, position: u32) -> (u32, u32) {
80+
let list = &*self.list.borrow();
81+
let message = list.get(position as usize).unwrap();
82+
83+
let ymd = glib::DateTime::from_unix_local(message.date() as i64)
84+
.unwrap()
85+
.ymd();
86+
87+
(
88+
if position == 0 {
89+
0
90+
} else {
91+
(0..position)
92+
.rev()
93+
.find(|i| {
94+
ymd != glib::DateTime::from_unix_local(
95+
list.get(*i as usize).unwrap().date() as i64,
96+
)
97+
.unwrap()
98+
.ymd()
99+
})
100+
.map(|i| i + 1)
101+
.unwrap_or(0)
102+
},
103+
(position + 1..list.len() as u32)
104+
.find(|i| {
105+
ymd != glib::DateTime::from_unix_local(
106+
list.get(*i as usize).unwrap().date() as i64,
107+
)
108+
.unwrap()
109+
.ymd()
110+
})
111+
.unwrap_or(list.len() as u32),
112+
)
113+
}
114+
}
78115
}
79116

80117
glib::wrapper! {
@@ -108,14 +145,7 @@ impl ChatHistoryModel {
108145
return Err(ChatHistoryError::AlreadyLoading);
109146
}
110147

111-
let oldest_message_id = imp
112-
.list
113-
.borrow()
114-
.iter()
115-
.rev()
116-
.find_map(|item| item.message())
117-
.map(|m| m.id())
118-
.unwrap_or_default();
148+
let oldest_message_id = imp.list.borrow().back().map(|m| m.id()).unwrap_or_default();
119149

120150
imp.is_loading.set(true);
121151

@@ -133,167 +163,36 @@ impl ChatHistoryModel {
133163
Ok(true)
134164
}
135165

136-
fn items_changed(&self, position: u32, removed: u32, added: u32) {
137-
let imp = self.imp();
138-
139-
// Insert day dividers where needed
140-
let added = {
141-
let position = position as usize;
142-
let added = added as usize;
143-
144-
let mut list = imp.list.borrow_mut();
145-
let mut previous_timestamp = if position + 1 < list.len() {
146-
list.get(position + 1)
147-
.and_then(|item| item.message_timestamp())
148-
} else {
149-
None
150-
};
151-
let mut dividers: Vec<(usize, model::ChatHistoryItem)> = vec![];
152-
153-
for (index, current) in list.range(position..position + added).enumerate().rev() {
154-
if let Some(current_timestamp) = current.message_timestamp() {
155-
if Some(current_timestamp.ymd()) != previous_timestamp.as_ref().map(|t| t.ymd())
156-
{
157-
let divider_pos = position + index + 1;
158-
dividers.push((
159-
divider_pos,
160-
model::ChatHistoryItem::for_day_divider(current_timestamp.clone()),
161-
));
162-
previous_timestamp = Some(current_timestamp);
163-
}
164-
}
165-
}
166-
167-
let dividers_len = dividers.len();
168-
for (position, item) in dividers {
169-
list.insert(position, item);
170-
}
171-
172-
(added + dividers_len) as u32
173-
};
174-
175-
// Check and remove no more needed day divider after removing messages
176-
let removed = {
177-
let mut removed = removed as usize;
178-
179-
if removed > 0 {
180-
let mut list = imp.list.borrow_mut();
181-
let position = position as usize;
182-
let item_before_removed = list.get(position);
183-
184-
if let Some(model::ChatHistoryItemType::DayDivider(_)) =
185-
item_before_removed.map(|i| i.type_())
186-
{
187-
let item_after_removed = if position > 0 {
188-
list.get(position - 1)
189-
} else {
190-
None
191-
};
192-
193-
match item_after_removed.map(|item| item.type_()) {
194-
None | Some(model::ChatHistoryItemType::DayDivider(_)) => {
195-
list.remove(position + removed);
196-
197-
removed += 1;
198-
}
199-
_ => {}
200-
}
201-
}
202-
}
203-
204-
removed as u32
205-
};
206-
207-
// Check and remove no more needed day divider after adding messages
208-
let (position, removed) = {
209-
let mut removed = removed;
210-
let mut position = position as usize;
211-
212-
if added > 0 && position > 0 {
213-
let mut list = imp.list.borrow_mut();
214-
let last_added_timestamp = list.get(position).unwrap().message_timestamp().unwrap();
215-
let next_item = list.get(position - 1);
216-
217-
if let Some(model::ChatHistoryItemType::DayDivider(date)) =
218-
next_item.map(|item| item.type_())
219-
{
220-
if date.ymd() == last_added_timestamp.ymd() {
221-
list.remove(position - 1);
222-
223-
removed += 1;
224-
position -= 1;
225-
}
226-
}
227-
}
228-
229-
(position as u32, removed)
230-
};
231-
232-
self.upcast_ref::<gio::ListModel>()
233-
.items_changed(position, removed, added);
234-
}
235-
236166
fn push_front(&self, message: model::Message) {
237-
self.imp()
238-
.list
239-
.borrow_mut()
240-
.push_front(model::ChatHistoryItem::for_message(message));
167+
self.imp().list.borrow_mut().push_front(message);
241168

242169
self.items_changed(0, 0, 1);
243170
}
244171

245172
fn append(&self, messages: Vec<model::Message>) {
246173
let imp = self.imp();
174+
247175
let added = messages.len();
248176

249177
imp.list.borrow_mut().reserve(added);
250178

251179
for message in messages {
252-
imp.list
253-
.borrow_mut()
254-
.push_back(model::ChatHistoryItem::for_message(message));
180+
imp.list.borrow_mut().push_back(message);
255181
}
256182

257183
let index = imp.list.borrow().len() - added;
258184
self.items_changed(index as u32, 0, added as u32);
259185
}
260186

261187
fn remove(&self, message: model::Message) {
262-
let imp = self.imp();
263-
264-
// Put this in a block, so that we only need to borrow the list once and the runtime
265-
// borrow checker does not panic in Self::items_changed when it borrows the list again.
266-
let index = {
267-
let mut list = imp.list.borrow_mut();
268-
269-
// The elements in this list are ordered. While the day dividers are ordered
270-
// only by their date time, the messages are additionally sorted by their id. We
271-
// can exploit this by applying a binary search.
272-
let index = list
273-
.binary_search_by(|m| match m.type_() {
274-
model::ChatHistoryItemType::Message(other_message) => {
275-
message.id().cmp(&other_message.id())
276-
}
277-
model::ChatHistoryItemType::DayDivider(date_time) => {
278-
let ordering = glib::DateTime::from_unix_utc(message.date() as i64)
279-
.unwrap()
280-
.cmp(date_time);
281-
if let Ordering::Equal = ordering {
282-
// We found the day divider of the message. Therefore, the message
283-
// must be among the following elements.
284-
Ordering::Greater
285-
} else {
286-
ordering
287-
}
288-
}
289-
})
290-
.unwrap();
188+
let mut list = self.imp().list.borrow_mut();
291189

190+
if let Ok(index) = list.binary_search_by(|m| message.id().cmp(&m.id())) {
292191
list.remove(index);
293-
index as u32
294-
};
295192

296-
self.items_changed(index, 1, 0);
193+
drop(list);
194+
self.items_changed(index as u32, 1, 0);
195+
}
297196
}
298197

299198
pub(crate) fn chat(&self) -> model::Chat {

src/model/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ mod basic_group;
33
mod chat;
44
mod chat_action;
55
mod chat_action_list;
6-
mod chat_history_item;
76
mod chat_history_model;
87
mod chat_list;
98
mod chat_list_item;
@@ -52,8 +51,6 @@ pub(crate) use self::chat::Chat;
5251
pub(crate) use self::chat::ChatType;
5352
pub(crate) use self::chat_action::ChatAction;
5453
pub(crate) use self::chat_action_list::ChatActionList;
55-
pub(crate) use self::chat_history_item::ChatHistoryItem;
56-
pub(crate) use self::chat_history_item::ChatHistoryItemType;
5754
pub(crate) use self::chat_history_model::ChatHistoryError;
5855
pub(crate) use self::chat_history_model::ChatHistoryModel;
5956
pub(crate) use self::chat_list::ChatList;

0 commit comments

Comments
 (0)