implemented the usage of the live location manager permission handling in the location sharing screen.

This commit is contained in:
Mauro Romito
2026-03-31 02:10:21 +02:00
committed by Mauro
parent b31d0cad75
commit d67c1a93db
11 changed files with 86 additions and 3 deletions
@@ -8,32 +8,41 @@
import Combine
import Foundation
import UIKit
typealias LocationSharingScreenViewModelType = StateStoreViewModelV2<LocationSharingScreenViewState, LocationSharingScreenViewAction>
class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, LocationSharingScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
private let timelineController: TimelineControllerProtocol
private let liveLocationManager: LiveLocationManagerProtocol
private let analytics: AnalyticsService
private let userIndicatorController: UserIndicatorControllerProtocol
private let notificationCenter: NotificationCenter
private let actionsSubject: PassthroughSubject<LocationSharingScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<LocationSharingScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
private var authorizationStatusSubscription: AnyCancellable?
init(interactionMode: LocationSharingInteractionMode,
mapURLBuilder: MapTilerURLBuilderProtocol,
liveLocationSharingEnabled: Bool,
roomProxy: JoinedRoomProxyProtocol,
timelineController: TimelineControllerProtocol,
liveLocationManager: LiveLocationManagerProtocol,
analytics: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol,
mediaProvider: MediaProviderProtocol) {
mediaProvider: MediaProviderProtocol,
notificationCenter: NotificationCenter = .default) {
self.roomProxy = roomProxy
self.timelineController = timelineController
self.liveLocationManager = liveLocationManager
self.analytics = analytics
self.userIndicatorController = userIndicatorController
self.notificationCenter = notificationCenter
super.init(initialViewState: .init(interactionMode: interactionMode,
mapURLBuilder: mapURLBuilder,
@@ -49,6 +58,8 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
switch viewAction {
case .close:
actionsSubject.send(.close)
case .startLiveLocation:
startLiveLocationSharing()
case .selectLocation:
guard let coordinate = state.bindings.mapCenterLocation else { return }
let uncertainty = state.isSharingUserLocation ? context.geolocationUncertainty : nil
@@ -75,6 +86,13 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
self?.updateShownUserProfile(members: members)
}
.store(in: &cancellables)
notificationCenter.publisher(for: UIApplication.didEnterBackgroundNotification)
.sink { [weak self] _ in
// Let's remove the subscription if the user backgrounds the app (maybe to change their location settings)
self?.authorizationStatusSubscription = nil
}
.store(in: &cancellables)
}
private func updateShownUserProfile(members: [RoomMemberProxyProtocol]) {
@@ -94,6 +112,49 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
}
}
private func startLiveLocationSharing() {
authorizationStatusSubscription = nil
let authStatus = liveLocationManager.authorizationStatus.value
switch authStatus {
case .authorizedAlways:
// TODO: Start sending live location updates to the room
break
case .notDetermined:
// This is to solve a race condition with map libre which always tries first
// to request the when in use permission, we wait for it and then try again
authorizationStatusSubscription = liveLocationManager.authorizationStatus
.filter { $0 != authStatus } // skip current status
.first() // this publisher only fires when there is an actual change, and if the user is done with permissions
.sink { [weak self] newValue in
guard newValue == .authorizedWhenInUse else { return }
self?.startLiveLocationSharing()
}
case .authorizedWhenInUse:
guard liveLocationManager.requestAlwaysAuthorizationIfPossible() else {
// Already requested before — iOS won't show the prompt again.
showMissingAlwaysAuthorizedAlert()
break
}
authorizationStatusSubscription = liveLocationManager.authorizationStatus
.filter { $0 != authStatus } // skip current status
.first() // this publisher only fires when there is an actual change, and if the user is done with permissions
.sink { newValue in
guard newValue == .authorizedAlways else { return }
// TODO: Start sending live location updates to the room
}
default:
showMissingAlwaysAuthorizedAlert()
}
}
private func showMissingAlwaysAuthorizedAlert() {
let action: () -> Void = { [weak self] in self?.actionsSubject.send(.openSystemSettings) }
state.bindings.alertInfo = .init(locationSharingViewError: .missingAlwaysAuthorization,
primaryButton: .init(title: L10n.actionNotNow, role: .cancel, action: nil),
secondaryButton: .init(title: L10n.commonSettings, action: action))
}
private func sendLocation(_ geoURI: GeoURI, isUserLocation: Bool) async {
guard case .success = await timelineController.sendLocation(body: geoURI.bodyMessage,
geoURI: geoURI,
@@ -157,6 +218,7 @@ extension LocationSharingScreenViewModel {
liveLocationSharingEnabled: liveLocationSharingEnabled,
roomProxy: JoinedRoomProxyMock(.init()),
timelineController: MockTimelineController(),
liveLocationManager: LiveLocationManagerMock(),
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
mediaProvider: MediaProviderMock(configuration: .init()))