From d304475ee9431cc1144a01ca3603187a7ec4800d Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 23 Apr 2026 16:07:52 +0200 Subject: [PATCH] Force a stop before starting a new session and resend last location on new starts --- .../Location/LiveLocationManager.swift | 21 +++++++++++++++++-- .../Sources/LiveLocationManagerTests.swift | 17 +++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/ElementX/Sources/Services/Location/LiveLocationManager.swift b/ElementX/Sources/Services/Location/LiveLocationManager.swift index 572c51ba1..7767c3b7b 100644 --- a/ElementX/Sources/Services/Location/LiveLocationManager.swift +++ b/ElementX/Sources/Services/Location/LiveLocationManager.swift @@ -28,6 +28,8 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana private var isUpdatingLocation = false + private var lastLocation: CLLocationCoordinate2D? + @MainActor init(clientProxy: ClientProxyProtocol, appSettings: AppSettings, @@ -76,10 +78,11 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana } func startLiveLocation(roomID: String, duration: Duration) async -> Result { - // Stop any existing session for this room first (e.g. one started from a different device) - // before starting a new one. + // Stop any existing session for this room first + var didAlreadyStopLocalSession = false if appSettings.liveLocationSharingTimeoutDatesByRoomID[roomID] != nil { await stopLiveLocation(roomID: roomID) + didAlreadyStopLocalSession = true } guard case .joined(let roomProxy) = await clientProxy.roomForIdentifier(roomID) else { @@ -87,6 +90,11 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana return .failure(.roomNotJoined) } + if !didAlreadyStopLocalSession { + // In case an existing session has been started from another device, let's try to stop it. + // It's a best effort thing, so we don't care if no session is present or if it fails. + _ = await roomProxy.stopLiveLocationShare() + } let result = await roomProxy.startLiveLocationShare(duration: duration) guard case .success = result else { @@ -97,6 +105,13 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana let timeoutDate = Date().addingTimeInterval(TimeInterval(duration.seconds)) appSettings.liveLocationSharingTimeoutDatesByRoomID[roomID] = timeoutDate + if isUpdatingLocation, let lastLocation { + // To make sure the newly started session is in sync with the existing ones, + // we re-send the last location received by the manager. + // Otherwise we would need to wait a distance filtered update. + locationUpdateSubject.send(lastLocation) + } + return .success(()) } @@ -145,6 +160,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana MXLog.verbose("Received location update via delegate, sending to rooms") locationUpdateSubject.send(location.coordinate) + lastLocation = location.coordinate } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { @@ -238,6 +254,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana MXLog.info("Stopping live location updates") locationManager.stopUpdatingLocation() isUpdatingLocation = false + lastLocation = nil } private func sendLocationToActiveRooms(_ coordinate: CLLocationCoordinate2D) async { diff --git a/UnitTests/Sources/LiveLocationManagerTests.swift b/UnitTests/Sources/LiveLocationManagerTests.swift index 1a46dae26..afccdd7aa 100644 --- a/UnitTests/Sources/LiveLocationManagerTests.swift +++ b/UnitTests/Sources/LiveLocationManagerTests.swift @@ -28,16 +28,25 @@ final class LiveLocationManagerTests { // MARK: - startLiveLocation @Test - func startLiveLocationWithNoExistingSession() async throws { + func startLiveLocationWithNoExistingLocalSession() async throws { setUp() let roomProxy = makeRoomProxy(roomID: "!room:matrix.org") clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) } + var callOrder: [String] = [] + roomProxy.stopLiveLocationShareClosure = { + callOrder.append("stop") + return .success(()) + } + roomProxy.startLiveLocationShareDurationClosure = { _ in + callOrder.append("start") + return .success(()) + } + let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300)) try result.get() - #expect(roomProxy.startLiveLocationShareDurationCalled) - #expect(!roomProxy.stopLiveLocationShareCalled) + #expect(callOrder == ["stop", "start"]) #expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room:matrix.org"] != nil) #expect(locationManagerMock.startUpdatingLocationCalled) } @@ -76,7 +85,7 @@ final class LiveLocationManagerTests { _ = await manager.startLiveLocation(roomID: "!room1:matrix.org", duration: .seconds(300)) - #expect(!roomProxy.stopLiveLocationShareCalled) + #expect(roomProxy.stopLiveLocationShareCalled) #expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room2:matrix.org"] != nil) }