feat(ui): Add active call information to the current notification item#6668
feat(ui): Add active call information to the current notification item#6668BillCarsonFr wants to merge 9 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #6668 +/- ##
========================================
Coverage 89.88% 89.88%
========================================
Files 396 396
Lines 110236 110361 +125
Branches 110236 110361 +125
========================================
+ Hits 99083 99200 +117
Misses 7385 7385
- Partials 3768 3776 +8 ☔ View full report in Codecov by Harness. |
poljar
left a comment
There was a problem hiding this comment.
This needs a bit more work.
| // Creates a legacy rtc membership event (state event). | ||
| pub fn call_membership_legacy_state( | ||
| &self, | ||
| user_id: OwnedUserId, | ||
| device_id: String, | ||
| ) -> EventBuilder<CallMemberEventContent> { | ||
| let focus = Focus::Livekit(LivekitFocus::new( | ||
| "room2".to_owned(), | ||
| "https://livekit2.com".to_owned(), | ||
| )); | ||
| self.event(CallMemberEventContent::new( | ||
| Application::Call(CallApplicationContent::new( | ||
| "".to_owned(), | ||
| ruma::events::call::member::CallScope::Room, | ||
| )), | ||
| owned_device_id!(device_id.clone()), | ||
| ActiveFocus::Livekit(ActiveLivekitFocus::new()), | ||
| vec![focus], | ||
| Some(MilliSecondsSinceUnixEpoch::now()), | ||
| Some(Duration::from_secs(3600)), | ||
| )) | ||
| .state_key(CallMemberStateKey::new(user_id, device_id.into(), true).as_ref()) | ||
| } | ||
|
|
||
| // Creates a legacy rtc membership event (state event). | ||
| pub fn call_membership_leave_legacy_state( | ||
| &self, | ||
| user_id: OwnedUserId, | ||
| device_id: String, | ||
| ) -> EventBuilder<CallMemberEventContent> { | ||
| self.event(CallMemberEventContent::new_empty(None)) | ||
| .state_key(CallMemberStateKey::new(user_id, device_id.into(), true).as_ref()) | ||
| } | ||
|
|
There was a problem hiding this comment.
These two should be a single function and you should add further functions under the EventBuilder<CallMemberEventContent> impl block.
Look at how this is done for the member() function of the EventBuilder.
| .state_key(CallMemberStateKey::new(user_id, device_id.into(), true).as_ref()) | ||
| } | ||
|
|
||
| // Creates a legacy rtc membership event (state event). |
There was a problem hiding this comment.
This is not a doc comment, an example in the doc comment would be welcome as well.
| /// The event ID of the active RTC notification item that should have | ||
| /// active_members populated | ||
| pub(crate) active_rtc_notification_event_id: Option<OwnedEventId>, |
There was a problem hiding this comment.
What does this mean? What are active_members, the thing in the ActiveCallInfo?
Is this the event ID which activated a call and contains the list of members which are participating in the call?
If so, isn't ActiveCallInfo the right place for this event ID?
Can we have Some RTC event ID, but None for the active_call field? I think the answer is yes, but not really sure why this would be so.
There was a problem hiding this comment.
If so, isn't ActiveCallInfo the right place for this event ID?
It was initially like that but I did a refactor because it didn't fell like it belongs to it.
Currently there is nothing tying an active call to a notification, the trick for now is to use the last notification event in the timeline as the anchor.
When the notification_event_id was part of the ActiveCallInfo, it was initially created with None. Then in the timeline controller when we have the last notification event we had to mutate the ActiveCallInfo to set the event_id now that we know it.
And also at every room_info stream update we were getting a new ActiveCallInfo update with None event_id, so had to mutate it again before saving.
The call_started_ts_millis also suffers from the same problem :/, I was close to remove it from the ActiveCallInfo
| flow: Flow::Local { txn_id, send_handle }, | ||
| should_add_new_items, | ||
| }; | ||
|
|
| self.event(RtcDeclineEventContent::new(notification_event_id)) | ||
| } | ||
|
|
||
| // Creates a legacy rtc membership event (state event). |
| /// active call info, and cleans up any previous notification that had | ||
| /// active members. | ||
| fn apply_active_call_to_last_rtc_notification(&mut self) { | ||
| let binding = self.items.clone(); |
There was a problem hiding this comment.
Isn't this potentially expensive? Do we really need to clone all items here? Doesn't seem like it as you're passing a reference to rfind_event_item()?
The variable name confuses me as well. What does binding even mean in this context?
There was a problem hiding this comment.
Actually, this whole function can be expressed much nicer, a lot of the indentation is noise which we can get rid of using the let Some/else pattern and the last part could be rewritten with let chains to avoid the unreachable as well:
diff --git a/crates/matrix-sdk-ui/src/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs
index 274d40ec73..4c6c9b1f4e 100644
--- a/crates/matrix-sdk-ui/src/timeline/event_handler.rs
+++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs
@@ -1212,76 +1212,74 @@
/// active call info, and cleans up any previous notification that had
/// active members.
fn apply_active_call_to_last_rtc_notification(&mut self) {
- let binding = self.items.clone();
- let last_notification = rfind_event_item(&binding, |it| {
+ let last_notification = rfind_event_item(&self.items, |it| {
matches!(it.content(), TimelineItemContent::RtcNotification { .. })
- });
-
- if let Some((idx, last_notification)) = last_notification {
- let last_notification_event_id = last_notification.event_id().map(ToOwned::to_owned);
- // Is this the same than previously?
- if let Some(prev_event_id) = &self.meta.active_rtc_notification_event_id {
- if Some(prev_event_id) == last_notification_event_id.as_ref() {
- // then no changes, the newest rtc_notification event have not change
- return;
- }
-
- // They are different, clean the old event
- // Find the previous notification item and clear its active_members
- if let Some((idx, prev_item)) =
- rfind_event_item(self.items, |it: &EventTimelineItem| {
- it.event_id() == Some(prev_event_id)
- })
- && let TimelineItemContent::RtcNotification {
- call_intent,
- declined_by,
- active_call_info,
- } = prev_item.content()
- {
- // Only clear if it has active members (not already empty)
- if active_call_info.is_some() {
- let new_content = TimelineItemContent::RtcNotification {
- call_intent: call_intent.clone(),
- declined_by: declined_by.clone(),
- active_call_info: None,
- };
- let new_event_item = prev_item.inner.with_content(new_content);
- let new_timeline_item =
- TimelineItem::new(new_event_item, prev_item.internal_id.clone());
- self.items.replace(idx, new_timeline_item);
- }
- }
- }
-
- // Update the new last content if needed
- if self.meta.active_call.is_some() {
- let new_content = match last_notification.content() {
- TimelineItemContent::RtcNotification {
- call_intent,
- declined_by,
- active_call_info: _,
- } => TimelineItemContent::RtcNotification {
- call_intent: call_intent.clone(),
- declined_by: declined_by.clone(),
- active_call_info: self
- .meta
- .active_call
- .clone()
- .map(|c| c.with_start_time(Some(self.ctx.timestamp))),
- },
- _ => unreachable!("Should be a rtc notification"),
+ })
+ .map(|(a, b)| (a, b.internal_id.clone(), b.clone()));
+
+ let Some((idx, internal_id, last_notification)) = last_notification else {
+ return;
+ };
+
+ let Some(prev_event_id) = self.meta.active_rtc_notification_event_id.as_deref() else {
+ return;
+ };
+
+ if Some(prev_event_id.as_ref()) == last_notification.event_id() {
+ return;
+ }
+
+ // They are different, clean the old event
+ // Find the previous notification item and clear its active_members
+ if let Some((idx, prev_item)) = rfind_event_item(self.items, |it: &EventTimelineItem| {
+ it.event_id() == Some(&prev_event_id)
+ }) && let TimelineItemContent::RtcNotification {
+ call_intent,
+ declined_by,
+ active_call_info,
+ } = prev_item.content()
+ {
+ // Only clear if it has active members (not already empty)
+ if active_call_info.is_some() {
+ let new_content = TimelineItemContent::RtcNotification {
+ call_intent: call_intent.clone(),
+ declined_by: declined_by.clone(),
+ active_call_info: None,
};
-
- let updated_event_item = last_notification.inner.with_content(new_content);
+ let new_event_item = prev_item.inner.with_content(new_content);
let new_timeline_item =
- TimelineItem::new(updated_event_item, last_notification.internal_id.clone());
+ TimelineItem::new(new_event_item, prev_item.internal_id.clone());
self.items.replace(idx, new_timeline_item);
-
- // Update the active_rtc_notification_event_id so that this event gets any new
- // updates of call membership
- self.meta.active_rtc_notification_event_id = last_notification_event_id;
}
}
+
+ // Update the new last content if needed
+ if self.meta.active_call.is_some()
+ && let TimelineItemContent::RtcNotification {
+ call_intent,
+ declined_by,
+ active_call_info: _,
+ } = last_notification.content()
+ {
+ let new_content = TimelineItemContent::RtcNotification {
+ call_intent: call_intent.clone(),
+ declined_by: declined_by.clone(),
+ active_call_info: self
+ .meta
+ .active_call
+ .clone()
+ .map(|c| c.with_start_time(Some(self.ctx.timestamp))),
+ };
+
+ let updated_event_item = last_notification.with_content(new_content);
+ let new_timeline_item = TimelineItem::new(updated_event_item, internal_id);
+ self.items.replace(idx, new_timeline_item);
+
+ // Update the active_rtc_notification_event_id so that this event gets any new
+ // updates of call membership
+ self.meta.active_rtc_notification_event_id =
+ last_notification.event_id().map(ToOwned::to_owned);
+ }
}
/// Try to recycle a local timeline item for the same event, or create a new| mut room_info: Subscriber<RoomInfo>, | ||
| timeline_controller: TimelineController, | ||
| ) { | ||
| // let mut prev_members: Vec<OwnedUserId> = Vec::new(); |
There was a problem hiding this comment.
Stale, commented out, line of code.
Note for the reviewer
The
rtc.notificationtimeline item is used to represent:When the
rtc.notificationis active, we want it to holds the information about the active call, so that client can simply build the UI.In order to do that a new Optional struct is added to the RTCNotificaiton Content
The timeline controller needs to be aware of the current active call:
active_call_infoas part of the meta data info of timeline controllerFor now we use the latest rtc notification item in the timeline (if 2 notifications are sent for the same call, the latest is used).
(see Writing changelog entries).
Signed-off-by: