11use std:: cell:: Cell ;
22use std:: cell:: RefCell ;
3- use std:: cmp:: Ordering ;
43use std:: collections:: VecDeque ;
54
65use gio:: prelude:: * ;
7- use gio:: subclass:: prelude:: * ;
86use glib:: clone;
97use gtk:: gio;
108use gtk:: glib;
9+ use gtk:: subclass:: prelude:: * ;
1110use once_cell:: sync:: Lazy ;
1211use 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,26 @@ 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+ find_section_border ( list, ymd, position as i32 - 1 , true )
92+ } ,
93+ find_section_border ( list, ymd, position as i32 + 1 , false ) ,
94+ )
95+ }
96+ }
7897}
7998
8099glib:: wrapper! {
@@ -111,9 +130,7 @@ impl ChatHistoryModel {
111130 let oldest_message_id = imp
112131 . list
113132 . borrow ( )
114- . iter ( )
115- . rev ( )
116- . find_map ( |item| item. message ( ) )
133+ . back ( )
117134 . map ( |m| m. id ( ) )
118135 . unwrap_or_default ( ) ;
119136
@@ -133,170 +150,73 @@ impl ChatHistoryModel {
133150 Ok ( true )
134151 }
135152
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-
236153 fn push_front ( & self , message : model:: Message ) {
237- self . imp ( )
238- . list
239- . borrow_mut ( )
240- . push_front ( model:: ChatHistoryItem :: for_message ( message) ) ;
154+ self . imp ( ) . list . borrow_mut ( ) . push_front ( message) ;
241155
242156 self . items_changed ( 0 , 0 , 1 ) ;
243157 }
244158
245159 fn append ( & self , messages : Vec < model:: Message > ) {
246160 let imp = self . imp ( ) ;
161+
247162 let added = messages. len ( ) ;
248163
249164 imp. list . borrow_mut ( ) . reserve ( added) ;
250165
251166 for message in messages {
252- imp. list
253- . borrow_mut ( )
254- . push_back ( model:: ChatHistoryItem :: for_message ( message) ) ;
167+ imp. list . borrow_mut ( ) . push_back ( message) ;
255168 }
256169
257170 let index = imp. list . borrow ( ) . len ( ) - added;
258171 self . items_changed ( index as u32 , 0 , added as u32 ) ;
259172 }
260173
261174 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 ( ) ;
175+ let mut list = self . imp ( ) . list . borrow_mut ( ) ;
291176
177+ if let Ok ( index) = list. binary_search_by ( |m| message. id ( ) . cmp ( & m. id ( ) ) ) {
292178 list. remove ( index) ;
293- index as u32
294- } ;
295179
296- self . items_changed ( index, 1 , 0 ) ;
180+ drop ( list) ;
181+ self . items_changed ( index as u32 , 1 , 0 ) ;
182+ }
297183 }
298184
299185 pub ( crate ) fn chat ( & self ) -> model:: Chat {
300186 self . imp ( ) . chat . upgrade ( ) . unwrap ( )
301187 }
302188}
189+
190+ fn find_section_border (
191+ messages : & VecDeque < model:: Message > ,
192+ ymd : ( i32 , i32 , i32 ) ,
193+ position : i32 ,
194+ backwards : bool ,
195+ ) -> u32 {
196+ if position < 0 {
197+ return position as u32 ;
198+ }
199+
200+ match messages. get ( position as usize ) {
201+ None => position as u32 ,
202+ Some ( message) => {
203+ let date_time = glib:: DateTime :: from_unix_local ( message. date ( ) as i64 ) . unwrap ( ) ;
204+ if date_time. ymd ( ) == ymd {
205+ find_section_border (
206+ messages,
207+ ymd,
208+ if backwards {
209+ position - 1
210+ } else {
211+ position + 1
212+ } ,
213+ backwards,
214+ )
215+ } else if backwards {
216+ position as u32 + 1
217+ } else {
218+ position as u32
219+ }
220+ }
221+ }
222+ }
0 commit comments