diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index a872f6361..369a36805 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -572,6 +572,7 @@ 6298AB0906DDD3525CD78C6B /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = 81DB3AB6CE996AB3954F4F03 /* KZFileWatchers */; }; 62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; }; 62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */; }; + 633400018E07D2DC7175B16E /* LiveLocationShareProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA12A7F5EF5C6D0B992869ED /* LiveLocationShareProxy.swift */; }; 633501761094E09DFBEBFFAD /* CopyTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */; }; 63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */; }; 6386EA3C898AD1A4BC1DC8A5 /* TimelineMediaPreviewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD40B92FCF20165658296AD /* TimelineMediaPreviewModifier.swift */; }; @@ -739,6 +740,7 @@ 7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */; }; 7C545FFEC9930F7247352593 /* SecurityAndPrivacyScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978092B01BEAB39F2C4389AE /* SecurityAndPrivacyScreenViewModel.swift */; }; 7C6376192F578E0BA801BFEC /* AnalyticsSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C64A14EE89928207E3B42B /* AnalyticsSettingsScreenModels.swift */; }; + 7C9A62022717060DFC1878D7 /* LiveLocationSharesServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C9C6EFC19EBB79E35806ED /* LiveLocationSharesServiceProtocol.swift */; }; 7C9BDF1FC7BD46C4676536AB /* AuthenticationStartScreenBackgroundImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682BC7BAF0EFEF512A8C5140 /* AuthenticationStartScreenBackgroundImage.swift */; }; 7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */; }; 7D249465ED00988EEEC14E05 /* JoinedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 867DC9530C42F7B5176BE465 /* JoinedRoomProxyMock.swift */; }; @@ -1181,6 +1183,7 @@ C8E1E4E06B7C7A3A8246FC9B /* MediaEventsTimelineScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8512B82404B1751D0BCC82D2 /* MediaEventsTimelineScreenCoordinator.swift */; }; C900127318820AD04D6C90B8 /* LabsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E43D8784B0054C048060FEB /* LabsScreenModels.swift */; }; C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */; }; + C9169AB88A0953C0B3D8601B /* LiveLocationSharesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89EE3CE58040FD2DF63DC23 /* LiveLocationSharesService.swift */; }; C960BACE42A9D8C535E8CB34 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1912062B53CE95E6F700DA60 /* Pagination.swift */; }; C969A62F3D9F14318481A33B /* KnockedRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858DA81F2ACF484B7CAD6AE4 /* KnockedRoomProxy.swift */; }; C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; }; @@ -1475,6 +1478,7 @@ FA2BBAE9FC5E2E9F960C0980 /* NavigationCoordinators.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */; }; FA53FA227FFBE469AFF32F71 /* TimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C585CE1F721A2770C70D47 /* TimelineControllerProtocol.swift */; }; FA5A7E32B1920FCB4EEDC1BA /* RoomDetailsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6493AC9979CEB1410302BFE3 /* RoomDetailsScreenCoordinator.swift */; }; + FA5FD4910EA871ACCED8D47B /* LiveLocationSharesMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4609B3576A5E612A95352EC1 /* LiveLocationSharesMock.swift */; }; FA71CD334F2D2289BEF0D749 /* SecureBackupRecoveryKeyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2FCA3D0F239B9E911B966B /* SecureBackupRecoveryKeyScreen.swift */; }; FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; }; FB0A9D06FC9122E37992D962 /* LayoutDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */; }; @@ -1998,6 +2002,7 @@ 45A4B934BA41D6C255900265 /* preview_video.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = preview_video.jpg; sourceTree = ""; }; 45CDF9A107BFE6C79B58D6B5 /* RoomMembersListScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModelProtocol.swift; sourceTree = ""; }; 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterProtocol.swift; sourceTree = ""; }; + 4609B3576A5E612A95352EC1 /* LiveLocationSharesMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationSharesMock.swift; sourceTree = ""; }; 4629710C0337ADD9C8909542 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Localizable.strings; sourceTree = ""; }; 466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenModels.swift; sourceTree = ""; }; 467498BEA681758BE2F80826 /* TimelineMediaPreviewDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewDetailsView.swift; sourceTree = ""; }; @@ -2538,6 +2543,7 @@ A9E6065FC6BC4A1B4C629E08 /* TimelineItemMenuActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenuActionProvider.swift; sourceTree = ""; }; A9E88667D393612FD5D84718 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/SAS.strings; sourceTree = ""; }; A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; + AA12A7F5EF5C6D0B992869ED /* LiveLocationShareProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationShareProxy.swift; sourceTree = ""; }; AA19C32BD97F45847724E09A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Untranslated.strings; sourceTree = ""; }; AAC9344689121887B74877AF /* UnitTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; AACE9B8E1A4AE79A7E2914F6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -2717,6 +2723,7 @@ C75FE3F524B575D53787868C /* TimelineMediaPreviewRedactConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewRedactConfirmationView.swift; sourceTree = ""; }; C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomList.swift; sourceTree = ""; }; C830A64609CBD152F06E0457 /* NotificationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationConstants.swift; sourceTree = ""; }; + C89EE3CE58040FD2DF63DC23 /* LiveLocationSharesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationSharesService.swift; sourceTree = ""; }; C90514BE9B8ACCBCF0AD2489 /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = ""; }; C95ADE8D9527523572532219 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = hu; path = hu.lproj/Localizable.stringsdict; sourceTree = ""; }; C97F8963B14EB0AF3940DDBF /* NotificationSettingsEditScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenRoomCell.swift; sourceTree = ""; }; @@ -2956,6 +2963,7 @@ F229480685F30BCB96C439EC /* AdvancedSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreen.swift; sourceTree = ""; }; F276F31C1AEC19E52B951B62 /* SendInviteConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendInviteConfirmationView.swift; sourceTree = ""; }; F2B94F1B0B5D9D42B15AA6E8 /* ChatsTabFlowCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsTabFlowCoordinatorStateMachine.swift; sourceTree = ""; }; + F2C9C6EFC19EBB79E35806ED /* LiveLocationSharesServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationSharesServiceProtocol.swift; sourceTree = ""; }; F2DC502B1A566E99969D34DD /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; F2E4EF80DFB8FE7C4469B15D /* RoomDirectorySearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreen.swift; sourceTree = ""; }; F3082001D373607455CB08A1 /* QRCodeErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeErrorView.swift; sourceTree = ""; }; @@ -3748,6 +3756,7 @@ 7F957320D0EB7D7B4E30C79D /* KnockRequestProxyMock.swift */, BA257D747DD7E6FFA5C2BE2D /* LinkNewDeviceServiceMock.swift */, 0B5DF0E888F66652F8C4CEC5 /* LiveLocationManagerMock.swift */, + 4609B3576A5E612A95352EC1 /* LiveLocationSharesMock.swift */, 6F65E4BB9E82EB8373207CF8 /* MediaProviderMock.swift */, 8DA1E8F287680C8ED25EDBAC /* NetworkMonitorMock.swift */, 840182D7A61402D5947DE094 /* NotificationItemProxyMock.swift */, @@ -5468,6 +5477,9 @@ DA46D6DD4B4AB17E1D45092E /* CLLocationManagerProtocol.swift */, D17F49E39CC38DAB7B305701 /* LiveLocationManager.swift */, 33752AE856E93CE62412B7A1 /* LiveLocationManagerProtocol.swift */, + AA12A7F5EF5C6D0B992869ED /* LiveLocationShareProxy.swift */, + C89EE3CE58040FD2DF63DC23 /* LiveLocationSharesService.swift */, + F2C9C6EFC19EBB79E35806ED /* LiveLocationSharesServiceProtocol.swift */, ); path = Location; sourceTree = ""; @@ -8407,6 +8419,10 @@ CD077E14FAADC444C5A80068 /* LiveLocationManagerProtocol.swift in Sources */, C8D0AC22E03F652118A2BB73 /* LiveLocationRoomTimelineItem.swift in Sources */, F7977C53B2B1D73030C69761 /* LiveLocationRoomTimelineView.swift in Sources */, + 633400018E07D2DC7175B16E /* LiveLocationShareProxy.swift in Sources */, + FA5FD4910EA871ACCED8D47B /* LiveLocationSharesMock.swift in Sources */, + C9169AB88A0953C0B3D8601B /* LiveLocationSharesService.swift in Sources */, + 7C9A62022717060DFC1878D7 /* LiveLocationSharesServiceProtocol.swift in Sources */, 9223E5F2A2CE0AFFDFF0AFFB /* LiveLocationSharingBannerView.swift in Sources */, 6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */, D9F80CE61BF8FF627FDB0543 /* LoadableImage.swift in Sources */, diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index b97d0aa43..41e70fa1e 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -10414,6 +10414,70 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable { return clearDraftThreadRootEventIDReturnValue } } + //MARK: - getLiveLocationSharesService + + var getLiveLocationSharesServiceUnderlyingCallsCount = 0 + var getLiveLocationSharesServiceCallsCount: Int { + get { + if Thread.isMainThread { + return getLiveLocationSharesServiceUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = getLiveLocationSharesServiceUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + getLiveLocationSharesServiceUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + getLiveLocationSharesServiceUnderlyingCallsCount = newValue + } + } + } + } + var getLiveLocationSharesServiceCalled: Bool { + return getLiveLocationSharesServiceCallsCount > 0 + } + + var getLiveLocationSharesServiceUnderlyingReturnValue: LiveLocationSharesServiceProtocol! + var getLiveLocationSharesServiceReturnValue: LiveLocationSharesServiceProtocol! { + get { + if Thread.isMainThread { + return getLiveLocationSharesServiceUnderlyingReturnValue + } else { + var returnValue: LiveLocationSharesServiceProtocol? = nil + DispatchQueue.main.sync { + returnValue = getLiveLocationSharesServiceUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + getLiveLocationSharesServiceUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + getLiveLocationSharesServiceUnderlyingReturnValue = newValue + } + } + } + } + var getLiveLocationSharesServiceClosure: (() async -> LiveLocationSharesServiceProtocol)? + + func getLiveLocationSharesService() async -> LiveLocationSharesServiceProtocol { + getLiveLocationSharesServiceCallsCount += 1 + if let getLiveLocationSharesServiceClosure = getLiveLocationSharesServiceClosure { + return await getLiveLocationSharesServiceClosure() + } else { + return getLiveLocationSharesServiceReturnValue + } + } //MARK: - startLiveLocationShare var startLiveLocationShareDurationUnderlyingCallsCount = 0 @@ -11904,6 +11968,14 @@ class LiveLocationManagerMock: LiveLocationManagerProtocol, @unchecked Sendable await stopLiveLocationRoomIDClosure?(roomID) } } +class LiveLocationSharesServiceMock: LiveLocationSharesServiceProtocol, @unchecked Sendable { + var liveLocationSharesPublisher: AnyPublisher<[LiveLocationShareProxy], Never> { + get { return underlyingLiveLocationSharesPublisher } + set(value) { underlyingLiveLocationSharesPublisher = value } + } + var underlyingLiveLocationSharesPublisher: AnyPublisher<[LiveLocationShareProxy], Never>! + +} class MediaLoaderMock: MediaLoaderProtocol, @unchecked Sendable { //MARK: - loadMediaContentForSource diff --git a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift index a8e23d390..37b9f6dd0 100644 --- a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift @@ -68,6 +68,8 @@ extension JoinedRoomProxyMock { typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher() identityStatusChangesPublisher = CurrentValueSubject([]).asCurrentValuePublisher() + getLiveLocationSharesServiceReturnValue = LiveLocationSharesServiceMock(.init()) + updateMembersClosure = { } setNameClosure = { _ in .success(()) } setTopicClosure = { _ in .success(()) } diff --git a/ElementX/Sources/Mocks/LiveLocationSharesMock.swift b/ElementX/Sources/Mocks/LiveLocationSharesMock.swift new file mode 100644 index 000000000..294184f6e --- /dev/null +++ b/ElementX/Sources/Mocks/LiveLocationSharesMock.swift @@ -0,0 +1,20 @@ +// +// 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 Combine +import Foundation + +struct LiveLocationSharesServiceMockConfiguration { + var shares: [LiveLocationShareProxy] = [] +} + +extension LiveLocationSharesServiceMock { + convenience init(_ configuration: LiveLocationSharesServiceMockConfiguration = .init()) { + self.init() + liveLocationSharesPublisher = CurrentValueSubject(configuration.shares).eraseToAnyPublisher() + } +} diff --git a/ElementX/Sources/Other/SDKListener.swift b/ElementX/Sources/Other/SDKListener.swift index 53a6059e7..4a012b7a7 100644 --- a/ElementX/Sources/Other/SDKListener.swift +++ b/ElementX/Sources/Other/SDKListener.swift @@ -197,6 +197,12 @@ extension SDKListener: KnockRequestsListener where T == [KnockRequest] { } } +extension SDKListener: LiveLocationShareListener where T == [LiveLocationShareUpdate] { + func onUpdate(updates: [LiveLocationShareUpdate]) { + onUpdateClosure(updates) + } +} + extension SDKListener: ThreadListEntriesListener where T == [ThreadListUpdate] { func onUpdate(diff: [ThreadListUpdate]) { onUpdateClosure(diff) diff --git a/ElementX/Sources/Services/Location/LiveLocationShareProxy.swift b/ElementX/Sources/Services/Location/LiveLocationShareProxy.swift new file mode 100644 index 000000000..21a2a4992 --- /dev/null +++ b/ElementX/Sources/Services/Location/LiveLocationShareProxy.swift @@ -0,0 +1,29 @@ +// +// 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 LiveLocationShareProxy: Hashable { + let userID: String + let geoURI: GeoURI? + let timestamp: Date + let timeoutDate: Date +} + +extension LiveLocationShareProxy { + init(liveLocationShare: LiveLocationShare) { + userID = liveLocationShare.userId + if let geoURI = liveLocationShare.lastLocation?.location.geoUri { + self.geoURI = GeoURI(string: geoURI) + } else { + geoURI = nil + } + timestamp = Date(timeIntervalSince1970: Double(liveLocationShare.startTs)) + timeoutDate = timestamp.addingTimeInterval(Double(liveLocationShare.timeout) / 1000) + } +} diff --git a/ElementX/Sources/Services/Location/LiveLocationSharesService.swift b/ElementX/Sources/Services/Location/LiveLocationSharesService.swift new file mode 100644 index 000000000..1075dc7b9 --- /dev/null +++ b/ElementX/Sources/Services/Location/LiveLocationSharesService.swift @@ -0,0 +1,72 @@ +// +// 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 Combine +import Foundation +import MatrixRustSDK + +final class LiveLocationSharesService: LiveLocationSharesServiceProtocol { + // periphery:ignore - required for instance retention in the rust codebase + private let liveLocationShares: LiveLocationShares + // periphery:ignore - required for instance retention in the rust codebase + private var observationToken: TaskHandle? + + private let liveLocationSharesSubject = PassthroughSubject<[LiveLocationShareProxy], Never>() + var liveLocationSharesPublisher: AnyPublisher<[LiveLocationShareProxy], Never> { + liveLocationSharesSubject.eraseToAnyPublisher() + } + + private var previousLiveLocationShares: [LiveLocationShareProxy] = [] + + init(liveLocationShares: LiveLocationShares) { + self.liveLocationShares = liveLocationShares + observationToken = liveLocationShares + .subscribe(listener: SDKListener { [weak self] updates in + guard let self else { return } + + MXLog.info("Received live location shares update") + let updatedShares = handleLiveLocationShareUpdates(updates) + liveLocationSharesSubject.send(updatedShares) + }) + } + + // MARK: - Private + + private func handleLiveLocationShareUpdates(_ updates: [LiveLocationShareUpdate]) -> [LiveLocationShareProxy] { + var shares = previousLiveLocationShares + + for update in updates { + switch update { + case .append(let values): + shares.append(contentsOf: values.map(LiveLocationShareProxy.init)) + case .clear: + shares.removeAll() + case .pushFront(let value): + shares.insert(LiveLocationShareProxy(liveLocationShare: value), at: 0) + case .pushBack(let value): + shares.append(LiveLocationShareProxy(liveLocationShare: value)) + case .popFront: + shares.removeFirst() + case .popBack: + shares.removeLast() + case .insert(let index, let value): + shares.insert(LiveLocationShareProxy(liveLocationShare: value), at: Int(index)) + case .set(let index, let value): + shares[Int(index)] = LiveLocationShareProxy(liveLocationShare: value) + case .remove(let index): + shares.remove(at: Int(index)) + case .truncate(let length): + shares.removeSubrange(Int(length).. { get } +} diff --git a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift index 96181ba69..d7a23d149 100644 --- a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift @@ -754,6 +754,10 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { // MARK: - Live Location + func getLiveLocationSharesService() async -> LiveLocationSharesServiceProtocol { + await LiveLocationSharesService(liveLocationShares: room.liveLocationShares()) + } + func startLiveLocationShare(duration: Duration) async -> Result { do { try await room.startLiveLocationShare(durationMillis: UInt64(duration.seconds * 1000)) diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index afbab3246..a7d68d73a 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -197,6 +197,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol { // MARK: - Live Location + func getLiveLocationSharesService() async -> LiveLocationSharesServiceProtocol + func startLiveLocationShare(duration: Duration) async -> Result func sendLiveLocation(geoURI: GeoURI) async -> Result func stopLiveLocationShare() async -> Result