1
1
//! A room screen is the UI page that displays a single Room's timeline of events/messages
2
2
//! along with a message input bar at the bottom.
3
3
4
- use std:: { borrow:: Cow , collections:: BTreeMap , ops:: { DerefMut , Range } , sync:: { Arc , Mutex } , time:: SystemTime } ;
4
+ use std:: { borrow:: Cow , collections:: { BTreeMap , HashSet } , ops:: { DerefMut , Range } , sync:: { Arc , Mutex } , time:: SystemTime } ;
5
5
6
6
use bytesize:: ByteSize ;
7
7
use imbl:: Vector ;
@@ -15,7 +15,7 @@ use matrix_sdk::{room::RoomMember, ruma::{
15
15
sticker:: StickerEventContent , Mentions } , matrix_uri:: MatrixId , uint, EventId , MatrixToUri , MatrixUri , MilliSecondsSinceUnixEpoch , OwnedEventId , OwnedMxcUri , OwnedRoomId
16
16
} , OwnedServerName } ;
17
17
use matrix_sdk_ui:: timeline:: {
18
- self , EventTimelineItem , InReplyToDetails , MemberProfileChange , RepliedToInfo , RoomMembershipChange , TimelineDetails , TimelineEventItemId , TimelineItem , TimelineItemContent , TimelineItemKind , VirtualTimelineItem
18
+ self , EventTimelineItem , InReplyToDetails , MemberProfileChange , MembershipChange , RepliedToInfo , RoomMembershipChange , TimelineDetails , TimelineEventItemId , TimelineItem , TimelineItemContent , TimelineItemKind , VirtualTimelineItem
19
19
} ;
20
20
use robius_location:: Coordinates ;
21
21
@@ -1339,23 +1339,9 @@ impl Widget for RoomScreen {
1339
1339
} ;
1340
1340
let room_id = & tl_state. room_id ;
1341
1341
let tl_items = & tl_state. items ;
1342
-
1343
1342
let grouped_items = group_small_state_events ( tl_items) ;
1344
1343
1345
- // for (_, group) in grouped_items.iter().enumerate() {
1346
- // match group {
1347
- // GroupedTimelineItem::GroupedSmallEvents(events) => {
1348
- // let summary = summarize_small_events(&events);
1349
- // log!("Summary: {:?}", summary);
1350
- // }
1351
- // _ => {}
1352
- // }
1353
- // }
1354
-
1355
- // Set the portal list's range based on the number of timeline items.
1356
- // let last_item_id = tl_items.len();
1357
1344
let last_item_id = grouped_items. len ( ) ;
1358
-
1359
1345
let list = list_ref. deref_mut ( ) ;
1360
1346
list. set_item_range ( cx, 0 , last_item_id) ;
1361
1347
@@ -1380,32 +1366,70 @@ impl Widget for RoomScreen {
1380
1366
TimelineItemContent :: Message ( message) => {
1381
1367
let prev_event = tl_idx. and_then ( |i| i. checked_sub ( 1 ) ) . and_then ( |i| tl_items. get ( i) ) ;
1382
1368
populate_message_view (
1383
- cx, list, item_id, room_id, event_tl_item,
1384
- MessageOrSticker :: Message ( message) , prev_event,
1385
- & mut tl_state. media_cache , & tl_state. user_power ,
1386
- item_drawn_status, room_screen_widget_uid,
1369
+ cx,
1370
+ list,
1371
+ item_id,
1372
+ room_id,
1373
+ event_tl_item,
1374
+ MessageOrSticker :: Message ( message) ,
1375
+ prev_event,
1376
+ & mut tl_state. media_cache ,
1377
+ & tl_state. user_power ,
1378
+ item_drawn_status,
1379
+ room_screen_widget_uid,
1387
1380
)
1388
1381
}
1389
1382
TimelineItemContent :: Sticker ( sticker) => {
1390
1383
let prev_event = tl_idx. and_then ( |i| i. checked_sub ( 1 ) ) . and_then ( |i| tl_items. get ( i) ) ;
1391
1384
populate_message_view (
1392
- cx, list, item_id, room_id, event_tl_item,
1393
- MessageOrSticker :: Sticker ( sticker. content ( ) ) , prev_event,
1394
- & mut tl_state. media_cache , & tl_state. user_power ,
1395
- item_drawn_status, room_screen_widget_uid,
1385
+ cx,
1386
+ list,
1387
+ item_id,
1388
+ room_id,
1389
+ event_tl_item,
1390
+ MessageOrSticker :: Sticker ( sticker. content ( ) ) ,
1391
+ prev_event,
1392
+ & mut tl_state. media_cache ,
1393
+ & tl_state. user_power ,
1394
+ item_drawn_status,
1395
+ room_screen_widget_uid,
1396
1396
)
1397
1397
}
1398
1398
TimelineItemContent :: RedactedMessage => populate_small_state_event (
1399
- cx, list, item_id, room_id, event_tl_item, & RedactedMessageEventMarker , item_drawn_status,
1399
+ cx,
1400
+ list,
1401
+ item_id,
1402
+ room_id,
1403
+ event_tl_item,
1404
+ & RedactedMessageEventMarker ,
1405
+ item_drawn_status,
1400
1406
) ,
1401
1407
TimelineItemContent :: MembershipChange ( membership_change) => populate_small_state_event (
1402
- cx, list, item_id, room_id, event_tl_item, membership_change, item_drawn_status,
1408
+ cx,
1409
+ list,
1410
+ item_id,
1411
+ room_id,
1412
+ event_tl_item,
1413
+ membership_change,
1414
+ item_drawn_status,
1403
1415
) ,
1404
1416
TimelineItemContent :: ProfileChange ( profile_change) => populate_small_state_event (
1405
- cx, list, item_id, room_id, event_tl_item, profile_change, item_drawn_status,
1417
+ cx,
1418
+ list,
1419
+ item_id,
1420
+ room_id,
1421
+ event_tl_item,
1422
+ profile_change,
1423
+ item_drawn_status,
1406
1424
) ,
1407
1425
TimelineItemContent :: OtherState ( other) => populate_small_state_event (
1408
- cx, list, item_id, room_id, event_tl_item, other, item_drawn_status,
1426
+ cx,
1427
+ list,
1428
+ item_id,
1429
+ room_id,
1430
+ event_tl_item,
1431
+ other,
1432
+ item_drawn_status,
1409
1433
) ,
1410
1434
unhandled => {
1411
1435
let item = list. item ( cx, item_id, live_id ! ( SmallStateEvent ) ) ;
@@ -1435,7 +1459,6 @@ impl Widget for RoomScreen {
1435
1459
tl_state. profile_drawn_since_last_update . insert ( tl_idx..tl_idx + 1 ) ;
1436
1460
}
1437
1461
}
1438
-
1439
1462
item. draw_all ( cx, & mut Scope :: empty ( ) ) ;
1440
1463
}
1441
1464
GroupedTimelineItem :: GroupedSmallEvents ( events) => {
@@ -1445,126 +1468,6 @@ impl Widget for RoomScreen {
1445
1468
item. draw_all ( cx, & mut Scope :: empty ( ) ) ;
1446
1469
}
1447
1470
} ;
1448
-
1449
- // let item = {
1450
- // let tl_idx = item_id;
1451
- // let Some(timeline_item) = tl_items.get(tl_idx) else {
1452
- // // This shouldn't happen (unless the timeline gets corrupted or some other weird error),
1453
- // // but we can always safely fill the item with an empty widget that takes up no space.
1454
- // list.item(cx, item_id, live_id!(Empty));
1455
- // continue;
1456
- // };
1457
-
1458
- // // Determine whether this item's content and profile have been drawn since the last update.
1459
- // // Pass this state to each of the `populate_*` functions so they can attempt to re-use
1460
- // // an item in the timeline's portallist that was previously populated, if one exists.
1461
- // let item_drawn_status = ItemDrawnStatus {
1462
- // content_drawn: tl_state.content_drawn_since_last_update.contains(&tl_idx),
1463
- // profile_drawn: tl_state.profile_drawn_since_last_update.contains(&tl_idx),
1464
- // };
1465
- // let (item, item_new_draw_status) = match timeline_item.kind() {
1466
- // TimelineItemKind::Event(event_tl_item) => match event_tl_item.content() {
1467
- // TimelineItemContent::Message(message) => {
1468
- // let prev_event = tl_idx.checked_sub(1).and_then(|i| tl_items.get(i));
1469
- // populate_message_view(
1470
- // cx,
1471
- // list,
1472
- // item_id,
1473
- // room_id,
1474
- // event_tl_item,
1475
- // MessageOrSticker::Message(message),
1476
- // prev_event,
1477
- // &mut tl_state.media_cache,
1478
- // &tl_state.user_power,
1479
- // item_drawn_status,
1480
- // room_screen_widget_uid,
1481
- // )
1482
- // }
1483
- // TimelineItemContent::Sticker(sticker) => {
1484
- // let prev_event = tl_idx.checked_sub(1).and_then(|i| tl_items.get(i));
1485
- // populate_message_view(
1486
- // cx,
1487
- // list,
1488
- // item_id,
1489
- // room_id,
1490
- // event_tl_item,
1491
- // MessageOrSticker::Sticker(sticker.content()),
1492
- // prev_event,
1493
- // &mut tl_state.media_cache,
1494
- // &tl_state.user_power,
1495
- // item_drawn_status,
1496
- // room_screen_widget_uid,
1497
- // )
1498
- // }
1499
- // TimelineItemContent::RedactedMessage => populate_small_state_event(
1500
- // cx,
1501
- // list,
1502
- // item_id,
1503
- // room_id,
1504
- // event_tl_item,
1505
- // &RedactedMessageEventMarker,
1506
- // item_drawn_status,
1507
- // ),
1508
- // TimelineItemContent::MembershipChange(membership_change) => {
1509
- // populate_small_state_event(
1510
- // cx,
1511
- // list,
1512
- // item_id,
1513
- // room_id,
1514
- // event_tl_item,
1515
- // membership_change,
1516
- // item_drawn_status,
1517
- // )
1518
- // },
1519
- // TimelineItemContent::ProfileChange(profile_change) => populate_small_state_event(
1520
- // cx,
1521
- // list,
1522
- // item_id,
1523
- // room_id,
1524
- // event_tl_item,
1525
- // profile_change,
1526
- // item_drawn_status,
1527
- // ),
1528
- // TimelineItemContent::OtherState(other) => populate_small_state_event(
1529
- // cx,
1530
- // list,
1531
- // item_id,
1532
- // room_id,
1533
- // event_tl_item,
1534
- // other,
1535
- // item_drawn_status,
1536
- // ),
1537
- // unhandled => {
1538
- // let item = list.item(cx, item_id, live_id!(SmallStateEvent));
1539
- // item.label(id!(content)).set_text(cx, &format!("[Unsupported] {:?}", unhandled));
1540
- // (item, ItemDrawnStatus::both_drawn())
1541
- // }
1542
- // }
1543
- // TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(millis)) => {
1544
- // let item = list.item(cx, item_id, live_id!(DateDivider));
1545
- // let text = unix_time_millis_to_datetime(millis)
1546
- // // format the time as a shortened date (Sat, Sept 5, 2021)
1547
- // .map(|dt| format!("{}", dt.date_naive().format("%a %b %-d, %Y")))
1548
- // .unwrap_or_else(|| format!("{:?}", millis));
1549
- // item.label(id!(date)).set_text(cx, &text);
1550
- // (item, ItemDrawnStatus::both_drawn())
1551
- // }
1552
- // TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker) => {
1553
- // let item = list.item(cx, item_id, live_id!(ReadMarker));
1554
- // (item, ItemDrawnStatus::both_drawn())
1555
- // }
1556
- // };
1557
-
1558
- // // Now that we've drawn the item, add its index to the set of drawn items.
1559
- // if item_new_draw_status.content_drawn {
1560
- // tl_state.content_drawn_since_last_update.insert(tl_idx .. tl_idx + 1);
1561
- // }
1562
- // if item_new_draw_status.profile_drawn {
1563
- // tl_state.profile_drawn_since_last_update.insert(tl_idx .. tl_idx + 1);
1564
- // }
1565
- // item
1566
- // };
1567
- // item.draw_all(cx, &mut Scope::empty());
1568
1471
}
1569
1472
}
1570
1473
DrawStep :: done ( )
@@ -4256,33 +4159,117 @@ fn group_small_state_events(
4256
4159
}
4257
4160
4258
4161
fn summarize_small_events ( events : & [ Arc < TimelineItem > ] ) -> String {
4259
- let mut parts = vec ! [ ] ;
4162
+ let mut joined = HashSet :: new ( ) ;
4163
+ let mut left = HashSet :: new ( ) ;
4164
+ let mut joined_and_left = HashSet :: new ( ) ;
4165
+
4166
+ let mut profile_displayname_updated = HashSet :: new ( ) ;
4167
+ let mut profile_avatar_updated = HashSet :: new ( ) ;
4168
+
4169
+ let mut redacted = 0 ;
4170
+ let mut state_changed = 0 ;
4171
+
4260
4172
for item in events {
4261
4173
if let TimelineItemKind :: Event ( e) = item. kind ( ) {
4262
4174
match e. content ( ) {
4263
4175
TimelineItemContent :: MembershipChange ( change) => {
4264
4176
let username = change. display_name ( ) . unwrap_or ( "someone" . to_owned ( ) ) ;
4265
- let Some ( preview) = text_preview_of_room_membership_change ( change) else {
4266
- parts. push ( "membership changed" . to_string ( ) ) ;
4267
- continue ;
4268
- } ;
4269
- parts. push ( format ! ( "{}" , & preview. format_with( & username) ) ) ;
4177
+ let joined_flag = matches ! ( change. change( ) , Some ( MembershipChange :: Joined ) ) ;
4178
+ let left_flag = matches ! ( change. change( ) , Some ( MembershipChange :: Left ) ) ;
4179
+
4180
+ if joined_flag && left_flag {
4181
+ joined_and_left. insert ( username) ;
4182
+ } else if joined_flag {
4183
+ if left. remove ( & username) {
4184
+ joined_and_left. insert ( username) ;
4185
+ } else {
4186
+ joined. insert ( username) ;
4187
+ }
4188
+ } else if left_flag {
4189
+ if joined. remove ( & username) {
4190
+ joined_and_left. insert ( username) ;
4191
+ } else {
4192
+ left. insert ( username) ;
4193
+ }
4194
+ }
4270
4195
}
4271
- TimelineItemContent :: ProfileChange ( _change) => {
4272
- // text_preview_of_member_profile_change(change, username).format_with(username);
4273
- parts. push ( "profile updated" . to_string ( ) ) ;
4196
+ TimelineItemContent :: ProfileChange ( change) => {
4197
+ let username = change. user_id ( ) . localpart ( ) . to_owned ( ) ;
4198
+
4199
+ if change. displayname_change ( ) . is_some ( ) {
4200
+ profile_displayname_updated. insert ( username. clone ( ) ) ;
4201
+ }
4202
+ if change. avatar_url_change ( ) . is_some ( ) {
4203
+ profile_avatar_updated. insert ( username) ;
4204
+ }
4274
4205
}
4275
4206
TimelineItemContent :: RedactedMessage => {
4276
- parts . push ( "a message was redacted" . to_string ( ) ) ;
4207
+ redacted += 1 ;
4277
4208
}
4278
4209
TimelineItemContent :: OtherState ( _) => {
4279
- parts . push ( "state changed" . to_string ( ) ) ;
4210
+ state_changed += 1 ;
4280
4211
}
4281
4212
_ => { }
4282
4213
}
4283
4214
}
4284
4215
}
4285
4216
4217
+ let mut profile_displayname_only = HashSet :: new ( ) ;
4218
+ let mut profile_avatar_only = HashSet :: new ( ) ;
4219
+ let mut profile_both = HashSet :: new ( ) ;
4220
+
4221
+ for user in profile_displayname_updated. union ( & profile_avatar_updated) {
4222
+ let in_display = profile_displayname_updated. contains ( user) ;
4223
+ let in_avatar = profile_avatar_updated. contains ( user) ;
4224
+
4225
+ match ( in_display, in_avatar) {
4226
+ ( true , true ) => { profile_both. insert ( user. clone ( ) ) ; }
4227
+ ( true , false ) => { profile_displayname_only. insert ( user. clone ( ) ) ; }
4228
+ ( false , true ) => { profile_avatar_only. insert ( user. clone ( ) ) ; }
4229
+ _ => { }
4230
+ }
4231
+ }
4232
+
4233
+ fn summarize_users ( action : & str , users : & HashSet < String > ) -> Option < String > {
4234
+ if users. is_empty ( ) {
4235
+ return None ;
4236
+ }
4237
+ let mut names: Vec < _ > = users. iter ( ) . collect ( ) ;
4238
+ names. sort ( ) ;
4239
+ match names. len ( ) {
4240
+ 1 => Some ( format ! ( "{} {}" , names[ 0 ] , action) ) ,
4241
+ 2 => Some ( format ! ( "{} and {} {}" , names[ 0 ] , names[ 1 ] , action) ) ,
4242
+ n => Some ( format ! ( "{} and {} others {}" , names[ 0 ] , n - 1 , action) ) ,
4243
+ }
4244
+ }
4245
+
4246
+ let mut parts = vec ! [ ] ;
4247
+
4248
+ if let Some ( text) = summarize_users ( "left" , & left) {
4249
+ parts. push ( text) ;
4250
+ }
4251
+ if let Some ( text) = summarize_users ( "joined" , & joined) {
4252
+ parts. push ( text) ;
4253
+ }
4254
+ if let Some ( text) = summarize_users ( "joined and left" , & joined_and_left) {
4255
+ parts. push ( text) ;
4256
+ }
4257
+ if let Some ( text) = summarize_users ( "updated their display name" , & profile_displayname_only) {
4258
+ parts. push ( text) ;
4259
+ }
4260
+ if let Some ( text) = summarize_users ( "updated their avatar" , & profile_avatar_only) {
4261
+ parts. push ( text) ;
4262
+ }
4263
+ if let Some ( text) = summarize_users ( "updated their display name and avatar" , & profile_both) {
4264
+ parts. push ( text) ;
4265
+ }
4266
+ if redacted > 0 {
4267
+ parts. push ( format ! ( "{} message(s) were redacted" , redacted) ) ;
4268
+ }
4269
+ if state_changed > 0 {
4270
+ parts. push ( format ! ( "{} state change(s)" , state_changed) ) ;
4271
+ }
4272
+
4286
4273
parts. join ( ", " )
4287
4274
}
4288
4275
0 commit comments