Render the live location timeline item (#5209)

* created the data for the timeline item

# Conflicts:
#	ElementX/Sources/Other/Logging/Tracing.swift
#	ElementX/Sources/Services/Authentication/AuthenticationService.swift
#	ElementX/Sources/Services/Authentication/LinkNewDeviceService.swift

* Implemented the LLS timeline item

* updated preview tests

* pr and design suggestions

* previewscrollview is debug only
This commit is contained in:
Mauro
2026-03-24 10:34:00 +01:00
committed by GitHub
parent 610c296125
commit b8d337e4ce
51 changed files with 799 additions and 93 deletions
@@ -0,0 +1,41 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
struct LiveLocationRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
/// Always empty just here for protocol conformance
var body: String {
""
}
let timestamp: Date
let isOutgoing: Bool
let isEditable: Bool
let canBeRepliedTo: Bool
let sender: TimelineItemSender
let content: LiveLocationRoomTimelineItemContent
var properties = RoomTimelineItemProperties()
}
struct LiveLocationRoomTimelineItemContent: Equatable {
let isLive: Bool
let timeoutDate: Date
let lastGeoURI: GeoURI?
}
extension LiveLocationRoomTimelineItemContent {
init(from content: MatrixRustSDK.LiveLocationContent, timestamp: Date) {
isLive = content.isLive
timeoutDate = timestamp.addingTimeInterval(Double(content.timeoutMs) / 1000)
lastGeoURI = content.locations.last.flatMap { GeoURI(string: $0.geoUri) }
}
}
@@ -41,11 +41,10 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
return buildRedactedTimelineItem(eventItemProxy, messageLikeContent, isOutgoing)
case .unableToDecrypt(let encryptedMessage):
return buildEncryptedTimelineItem(eventItemProxy, messageLikeContent, encryptedMessage, isOutgoing)
case .liveLocation(let content):
return buildLiveLocationTimelineItem(eventItemProxy, messageLikeContent, content, isOutgoing)
case .other:
return nil // We shouldn't receive these without asking for custom event types.
case .liveLocation:
// TODO: Implement
return nil
}
case .failedToParseMessageLike(let eventType, let error):
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
@@ -433,6 +432,27 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
encryptionForwarder: eventItemProxy.forwarder))
}
private func buildLiveLocationTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
_ messageLikeContent: MsgLikeContent,
_ liveLocationContent: LiveLocationContent,
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
LiveLocationRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp,
isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
sender: eventItemProxy.sender,
content: .init(from: liveLocationContent, timestamp: eventItemProxy.timestamp),
properties: .init(replyDetails: buildTimelineItemReplyDetails(messageLikeContent.inReplyTo),
isThreaded: messageLikeContent.threadRoot != nil,
threadSummary: buildTimelineItemThreadSummary(messageLikeContent.threadSummary),
reactions: buildAggregatedReactions(messageLikeContent.reactions),
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
encryptionForwarder: eventItemProxy.forwarder))
}
private func buildRedactedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
_ messageLikeContent: MsgLikeContent,
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
@@ -850,6 +870,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyContent = .message(.text(.init(body: body)))
case .redacted:
replyContent = .redacted
case .liveLocation:
replyContent = .liveLocation
default:
replyContent = .message(.text(.init(body: L10n.commonUnsupportedEvent)))
}
@@ -75,6 +75,8 @@ struct RoomTimelineItemView: View {
CallInviteRoomTimelineView(timelineItem: item)
case .callNotification(let item):
CallNotificationRoomTimelineView(timelineItem: item)
case .liveLocation(let item):
LiveLocationRoomTimelineView(timelineItem: item)
}
}
@@ -64,6 +64,7 @@ enum RoomTimelineItemType: Equatable {
case voice(VoiceMessageRoomTimelineItem)
case callInvite(CallInviteRoomTimelineItem)
case callNotification(CallNotificationRoomTimelineItem)
case liveLocation(LiveLocationRoomTimelineItem)
init(item: RoomTimelineItemProtocol) {
switch item {
@@ -111,6 +112,8 @@ enum RoomTimelineItemType: Equatable {
self = .callInvite(item)
case let item as CallNotificationRoomTimelineItem:
self = .callNotification(item)
case let item as LiveLocationRoomTimelineItem:
self = .liveLocation(item)
default:
fatalError("Unknown timeline item")
}
@@ -139,7 +142,8 @@ enum RoomTimelineItemType: Equatable {
.poll(let item as RoomTimelineItemProtocol),
.voice(let item as RoomTimelineItemProtocol),
.callInvite(let item as RoomTimelineItemProtocol),
.callNotification(let item as RoomTimelineItemProtocol):
.callNotification(let item as RoomTimelineItemProtocol),
.liveLocation(let item as RoomTimelineItemProtocol):
return item.id
}
}