matrix_sdk_ui/timeline/event_item/content/live_location.rs
1// Copyright 2026 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Timeline item content for live location sharing (MSC3489).
16//!
17//! Live location sharing uses two event types:
18//! - `org.matrix.msc3672.beacon_info` (state event): starts/stops a sharing
19//! session and creates the timeline item represented by
20//! [`LiveLocationState`].
21//! - `org.matrix.msc3672.beacon` (message-like event): periodic location
22//! updates that are aggregated onto the parent [`LiveLocationState`] item.
23
24use ruma::{
25 MilliSecondsSinceUnixEpoch,
26 events::{beacon_info::BeaconInfoEventContent, location::AssetType},
27};
28
29/// A single location update received from a beacon event.
30///
31/// Created from an `org.matrix.msc3672.beacon` message-like event and
32/// aggregated onto the parent [`LiveLocationState`] timeline item.
33#[derive(Clone, Debug)]
34pub struct BeaconInfo {
35 /// The geo URI carrying the user's coordinates (e.g.
36 /// `"geo:51.5008,0.1247;u=35"`).
37 pub(in crate::timeline) geo_uri: String,
38
39 /// Timestamp of this location update (from the beacon event's
40 /// `org.matrix.msc3488.ts` field).
41 pub(in crate::timeline) ts: MilliSecondsSinceUnixEpoch,
42
43 /// An optional human-readable description of the location.
44 pub(in crate::timeline) description: Option<String>,
45}
46
47impl BeaconInfo {
48 /// The geo URI of this location update.
49 pub fn geo_uri(&self) -> &str {
50 &self.geo_uri
51 }
52
53 /// The timestamp of this location update.
54 pub fn ts(&self) -> MilliSecondsSinceUnixEpoch {
55 self.ts
56 }
57
58 /// An optional human-readable description of this location.
59 pub fn description(&self) -> Option<&str> {
60 self.description.as_deref()
61 }
62}
63
64/// The state of a live location sharing session.
65///
66/// Created when a `org.matrix.msc3672.beacon_info` state event is received.
67/// Subsequent `org.matrix.msc3672.beacon` message-like events are aggregated
68/// onto this item, appending to [`LiveLocationState::locations`].
69///
70/// When a user stops sharing (a new `beacon_info` with `live: false` arrives)
71/// a *separate* timeline item is created for the stop event. The original
72/// item's liveness can be checked via [`LiveLocationState::is_live`], which
73/// internally checks both the `live` flag and the session timeout.
74#[derive(Clone, Debug)]
75pub struct LiveLocationState {
76 /// The content of the `beacon_info` state event that created this item.
77 pub(in crate::timeline) beacon_info: BeaconInfoEventContent,
78
79 /// All location updates aggregated onto this session, kept sorted by
80 /// timestamp.
81 pub(in crate::timeline) locations: Vec<BeaconInfo>,
82}
83
84impl LiveLocationState {
85 /// Create a new [`LiveLocationState`] from the given
86 /// [`BeaconInfoEventContent`].
87 pub fn new(beacon_info: BeaconInfoEventContent) -> Self {
88 Self { beacon_info, locations: Vec::new() }
89 }
90
91 /// Add a location update. Keeps the internal list sorted by timestamp so
92 /// that [`LiveLocationState::latest_location`] always returns the most
93 /// recent one.
94 pub(in crate::timeline) fn add_location(&mut self, location: BeaconInfo) {
95 match self.locations.binary_search_by_key(&location.ts, |l| l.ts) {
96 Ok(_) => (), // Duplicate timestamp, do nothing.
97 Err(index) => self.locations.insert(index, location),
98 }
99 }
100
101 /// Remove the location update with the given timestamp. Used when
102 /// unapplying an aggregation (e.g. event cache moves an event).
103 pub(in crate::timeline) fn remove_location(&mut self, ts: MilliSecondsSinceUnixEpoch) {
104 self.locations.retain(|l| l.ts != ts);
105 }
106
107 /// All accumulated location updates, sorted by timestamp (oldest first).
108 pub fn locations(&self) -> &[BeaconInfo] {
109 &self.locations
110 }
111
112 /// The most recent location update, if any have been received.
113 pub fn latest_location(&self) -> Option<&BeaconInfo> {
114 self.locations.last()
115 }
116
117 /// Whether this live location share is still active.
118 ///
119 /// Returns `false` once the `live` flag has been set to `false` **or**
120 /// the session's timeout has elapsed.
121 pub fn is_live(&self) -> bool {
122 self.beacon_info.is_live()
123 }
124
125 /// An optional human-readable description for this sharing session
126 /// (from the originating `beacon_info` event).
127 pub fn description(&self) -> Option<&str> {
128 self.beacon_info.description.as_deref()
129 }
130
131 /// The duration that the location sharing will be live.
132 ///
133 /// Meaning that the location will stop being shared at `ts + timeout`.
134 pub fn timeout(&self) -> std::time::Duration {
135 self.beacon_info.timeout
136 }
137
138 /// The asset type of the beacon (e.g. `Sender` for the user's own
139 /// location, `Pin` for a fixed point of interest).
140 pub fn asset_type(&self) -> AssetType {
141 self.beacon_info.asset.type_.clone()
142 }
143
144 /// Update this session with a stop `beacon_info` event (one where
145 /// `live` is `false`). This replaces the stored content so that
146 /// [`LiveLocationState::is_live`] will return `false`.
147 pub(in crate::timeline) fn stop(&mut self, beacon_info: BeaconInfoEventContent) {
148 assert!(!beacon_info.is_live(), "A stop `beacon_info` event must not be live.");
149 self.beacon_info = beacon_info;
150 }
151}