Skip to main content

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}