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,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
80117glib:: 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 {
0 commit comments