implemented the usage of the live location manager permission handling in the location sharing screen.
This commit is contained in:
@@ -409,6 +409,7 @@
|
|||||||
"dialog_file_too_large_to_upload_title" = "The file size is too large to upload";
|
"dialog_file_too_large_to_upload_title" = "The file size is too large to upload";
|
||||||
"dialog_permission_camera" = "In order to let the application use the camera, please grant the permission in the system settings.";
|
"dialog_permission_camera" = "In order to let the application use the camera, please grant the permission in the system settings.";
|
||||||
"dialog_permission_generic" = "Please grant the permission in the system settings.";
|
"dialog_permission_generic" = "Please grant the permission in the system settings.";
|
||||||
|
"dialog_permission_live_location_description_ios" = "To share your live location, %1$@ needs location access when the app is in the background. Go to Settings > Location and select Always";
|
||||||
"dialog_permission_location_description_ios" = "To share your current location, %1$@ needs location access. Go to Settings > Location.";
|
"dialog_permission_location_description_ios" = "To share your current location, %1$@ needs location access. Go to Settings > Location.";
|
||||||
"dialog_permission_location_title_ios" = "%1$@ does not have access to your location.";
|
"dialog_permission_location_title_ios" = "%1$@ does not have access to your location.";
|
||||||
"dialog_permission_microphone" = "In order to let the application use the microphone, please grant the permission in the system settings.";
|
"dialog_permission_microphone" = "In order to let the application use the microphone, please grant the permission in the system settings.";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"NSCameraUsageDescription" = "To take pictures or videos and send them as a message Element X needs access to the camera.";
|
"NSCameraUsageDescription" = "To take pictures or videos and send them as a message Element X needs access to the camera.";
|
||||||
"NSFaceIDUsageDescription" = "Face ID is used to access your app.";
|
"NSFaceIDUsageDescription" = "Face ID is used to access your app.";
|
||||||
"NSLocationAlwaysAndWhenInUseUsageDescription" = "To share your live location, Element X needs location access when the app is in the background.";
|
"NSLocationAlwaysAndWhenInUseUsageDescription" = "To share your live location, Element X needs location access when the app is in the background.";
|
||||||
"NSLocationWhenInUseUsageDescription" = "Grant location access so that Element X can share your location.";
|
"NSLocationWhenInUseUsageDescription" = "Grant location access so that Element X can share your location.";
|
||||||
"NSMicrophoneUsageDescription" = "To record and send messages with audio, Element X needs to access the microphone.";
|
"NSMicrophoneUsageDescription" = "To record and send messages with audio, Element X needs to access the microphone.";
|
||||||
"NSPhotoLibraryUsageDescription" = "This lets you save images and videos to your photo library.";
|
"NSPhotoLibraryUsageDescription" = "This lets you save images and videos to your photo library.";
|
||||||
|
|||||||
@@ -409,6 +409,7 @@
|
|||||||
"dialog_file_too_large_to_upload_title" = "The file size is too large to upload";
|
"dialog_file_too_large_to_upload_title" = "The file size is too large to upload";
|
||||||
"dialog_permission_camera" = "In order to let the application use the camera, please grant the permission in the system settings.";
|
"dialog_permission_camera" = "In order to let the application use the camera, please grant the permission in the system settings.";
|
||||||
"dialog_permission_generic" = "Please grant the permission in the system settings.";
|
"dialog_permission_generic" = "Please grant the permission in the system settings.";
|
||||||
|
"dialog_permission_live_location_description_ios" = "To share your live location, %1$@ needs location access when the app is in the background. Go to Settings > Location and select Always";
|
||||||
"dialog_permission_location_description_ios" = "To share your current location, %1$@ needs location access. Go to Settings > Location.";
|
"dialog_permission_location_description_ios" = "To share your current location, %1$@ needs location access. Go to Settings > Location.";
|
||||||
"dialog_permission_location_title_ios" = "%1$@ does not have access to your location.";
|
"dialog_permission_location_title_ios" = "%1$@ does not have access to your location.";
|
||||||
"dialog_permission_microphone" = "In order to let the application use the microphone, please grant the permission in the system settings.";
|
"dialog_permission_microphone" = "In order to let the application use the microphone, please grant the permission in the system settings.";
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
liveLocationSharingEnabled: flowParameters.appSettings.liveLocationSharingEnabled,
|
liveLocationSharingEnabled: flowParameters.appSettings.liveLocationSharingEnabled,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
|
liveLocationManager: flowParameters.userSession.liveLocationManager,
|
||||||
appMediator: flowParameters.appMediator,
|
appMediator: flowParameters.appMediator,
|
||||||
analytics: flowParameters.analytics,
|
analytics: flowParameters.analytics,
|
||||||
userIndicatorController: flowParameters.userIndicatorController,
|
userIndicatorController: flowParameters.userIndicatorController,
|
||||||
|
|||||||
@@ -1139,6 +1139,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
liveLocationSharingEnabled: flowParameters.appSettings.liveLocationSharingEnabled,
|
liveLocationSharingEnabled: flowParameters.appSettings.liveLocationSharingEnabled,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
|
liveLocationManager: flowParameters.userSession.liveLocationManager,
|
||||||
appMediator: flowParameters.appMediator,
|
appMediator: flowParameters.appMediator,
|
||||||
analytics: flowParameters.analytics,
|
analytics: flowParameters.analytics,
|
||||||
userIndicatorController: flowParameters.userIndicatorController,
|
userIndicatorController: flowParameters.userIndicatorController,
|
||||||
|
|||||||
@@ -936,6 +936,10 @@ internal enum L10n {
|
|||||||
internal static var dialogPermissionCamera: String { return L10n.tr("Localizable", "dialog_permission_camera") }
|
internal static var dialogPermissionCamera: String { return L10n.tr("Localizable", "dialog_permission_camera") }
|
||||||
/// Please grant the permission in the system settings.
|
/// Please grant the permission in the system settings.
|
||||||
internal static var dialogPermissionGeneric: String { return L10n.tr("Localizable", "dialog_permission_generic") }
|
internal static var dialogPermissionGeneric: String { return L10n.tr("Localizable", "dialog_permission_generic") }
|
||||||
|
/// To share your live location, %1$@ needs location access when the app is in the background. Go to Settings > Location and select Always
|
||||||
|
internal static func dialogPermissionLiveLocationDescriptionIos(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "dialog_permission_live_location_description_ios", String(describing: p1))
|
||||||
|
}
|
||||||
/// To share your current location, %1$@ needs location access. Go to Settings > Location.
|
/// To share your current location, %1$@ needs location access. Go to Settings > Location.
|
||||||
internal static func dialogPermissionLocationDescriptionIos(_ p1: Any) -> String {
|
internal static func dialogPermissionLocationDescriptionIos(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "dialog_permission_location_description_ios", String(describing: p1))
|
return L10n.tr("Localizable", "dialog_permission_location_description_ios", String(describing: p1))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ struct LocationSharingScreenCoordinatorParameters {
|
|||||||
let liveLocationSharingEnabled: Bool
|
let liveLocationSharingEnabled: Bool
|
||||||
let roomProxy: JoinedRoomProxyProtocol
|
let roomProxy: JoinedRoomProxyProtocol
|
||||||
let timelineController: TimelineControllerProtocol
|
let timelineController: TimelineControllerProtocol
|
||||||
|
let liveLocationManager: LiveLocationManagerProtocol
|
||||||
let appMediator: AppMediatorProtocol
|
let appMediator: AppMediatorProtocol
|
||||||
let analytics: AnalyticsService
|
let analytics: AnalyticsService
|
||||||
let userIndicatorController: UserIndicatorControllerProtocol
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
@@ -44,6 +45,7 @@ final class LocationSharingScreenCoordinator: CoordinatorProtocol {
|
|||||||
liveLocationSharingEnabled: parameters.liveLocationSharingEnabled,
|
liveLocationSharingEnabled: parameters.liveLocationSharingEnabled,
|
||||||
roomProxy: parameters.roomProxy,
|
roomProxy: parameters.roomProxy,
|
||||||
timelineController: parameters.timelineController,
|
timelineController: parameters.timelineController,
|
||||||
|
liveLocationManager: parameters.liveLocationManager,
|
||||||
analytics: parameters.analytics,
|
analytics: parameters.analytics,
|
||||||
userIndicatorController: parameters.userIndicatorController,
|
userIndicatorController: parameters.userIndicatorController,
|
||||||
mediaProvider: parameters.mediaProvider)
|
mediaProvider: parameters.mediaProvider)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import MatrixRustSDK
|
|||||||
|
|
||||||
enum LocationSharingViewError: Error, Hashable {
|
enum LocationSharingViewError: Error, Hashable {
|
||||||
case missingAuthorization
|
case missingAuthorization
|
||||||
|
case missingAlwaysAuthorization
|
||||||
case mapError(MapLibreError)
|
case mapError(MapLibreError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +143,7 @@ struct LocationSharingScreenBindings {
|
|||||||
enum LocationSharingScreenViewAction {
|
enum LocationSharingScreenViewAction {
|
||||||
case close
|
case close
|
||||||
case selectLocation
|
case selectLocation
|
||||||
|
case startLiveLocation
|
||||||
case centerToUser
|
case centerToUser
|
||||||
case userDidPan
|
case userDidPan
|
||||||
}
|
}
|
||||||
@@ -157,6 +159,12 @@ extension AlertInfo where T == LocationSharingViewError {
|
|||||||
message: L10n.dialogPermissionLocationDescriptionIos(InfoPlistReader.main.bundleDisplayName),
|
message: L10n.dialogPermissionLocationDescriptionIos(InfoPlistReader.main.bundleDisplayName),
|
||||||
primaryButton: primaryButton,
|
primaryButton: primaryButton,
|
||||||
secondaryButton: secondaryButton)
|
secondaryButton: secondaryButton)
|
||||||
|
case .missingAlwaysAuthorization:
|
||||||
|
self.init(id: error,
|
||||||
|
title: L10n.dialogAllowAccess,
|
||||||
|
message: L10n.dialogPermissionLiveLocationDescriptionIos(InfoPlistReader.main.bundleDisplayName),
|
||||||
|
primaryButton: primaryButton,
|
||||||
|
secondaryButton: secondaryButton)
|
||||||
case .mapError(.failedLoadingMap):
|
case .mapError(.failedLoadingMap):
|
||||||
self.init(id: error,
|
self.init(id: error,
|
||||||
title: L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName),
|
title: L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName),
|
||||||
|
|||||||
@@ -8,32 +8,41 @@
|
|||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
typealias LocationSharingScreenViewModelType = StateStoreViewModelV2<LocationSharingScreenViewState, LocationSharingScreenViewAction>
|
typealias LocationSharingScreenViewModelType = StateStoreViewModelV2<LocationSharingScreenViewState, LocationSharingScreenViewAction>
|
||||||
|
|
||||||
class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, LocationSharingScreenViewModelProtocol {
|
class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, LocationSharingScreenViewModelProtocol {
|
||||||
private let roomProxy: JoinedRoomProxyProtocol
|
private let roomProxy: JoinedRoomProxyProtocol
|
||||||
private let timelineController: TimelineControllerProtocol
|
private let timelineController: TimelineControllerProtocol
|
||||||
|
private let liveLocationManager: LiveLocationManagerProtocol
|
||||||
private let analytics: AnalyticsService
|
private let analytics: AnalyticsService
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
|
private let notificationCenter: NotificationCenter
|
||||||
|
|
||||||
private let actionsSubject: PassthroughSubject<LocationSharingScreenViewModelAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<LocationSharingScreenViewModelAction, Never> = .init()
|
||||||
var actions: AnyPublisher<LocationSharingScreenViewModelAction, Never> {
|
var actions: AnyPublisher<LocationSharingScreenViewModelAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var authorizationStatusSubscription: AnyCancellable?
|
||||||
|
|
||||||
init(interactionMode: LocationSharingInteractionMode,
|
init(interactionMode: LocationSharingInteractionMode,
|
||||||
mapURLBuilder: MapTilerURLBuilderProtocol,
|
mapURLBuilder: MapTilerURLBuilderProtocol,
|
||||||
liveLocationSharingEnabled: Bool,
|
liveLocationSharingEnabled: Bool,
|
||||||
roomProxy: JoinedRoomProxyProtocol,
|
roomProxy: JoinedRoomProxyProtocol,
|
||||||
timelineController: TimelineControllerProtocol,
|
timelineController: TimelineControllerProtocol,
|
||||||
|
liveLocationManager: LiveLocationManagerProtocol,
|
||||||
analytics: AnalyticsService,
|
analytics: AnalyticsService,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol,
|
userIndicatorController: UserIndicatorControllerProtocol,
|
||||||
mediaProvider: MediaProviderProtocol) {
|
mediaProvider: MediaProviderProtocol,
|
||||||
|
notificationCenter: NotificationCenter = .default) {
|
||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
self.timelineController = timelineController
|
self.timelineController = timelineController
|
||||||
|
self.liveLocationManager = liveLocationManager
|
||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
|
self.notificationCenter = notificationCenter
|
||||||
|
|
||||||
super.init(initialViewState: .init(interactionMode: interactionMode,
|
super.init(initialViewState: .init(interactionMode: interactionMode,
|
||||||
mapURLBuilder: mapURLBuilder,
|
mapURLBuilder: mapURLBuilder,
|
||||||
@@ -49,6 +58,8 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
|||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .close:
|
case .close:
|
||||||
actionsSubject.send(.close)
|
actionsSubject.send(.close)
|
||||||
|
case .startLiveLocation:
|
||||||
|
startLiveLocationSharing()
|
||||||
case .selectLocation:
|
case .selectLocation:
|
||||||
guard let coordinate = state.bindings.mapCenterLocation else { return }
|
guard let coordinate = state.bindings.mapCenterLocation else { return }
|
||||||
let uncertainty = state.isSharingUserLocation ? context.geolocationUncertainty : nil
|
let uncertainty = state.isSharingUserLocation ? context.geolocationUncertainty : nil
|
||||||
@@ -75,6 +86,13 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
|||||||
self?.updateShownUserProfile(members: members)
|
self?.updateShownUserProfile(members: members)
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.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]) {
|
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 {
|
private func sendLocation(_ geoURI: GeoURI, isUserLocation: Bool) async {
|
||||||
guard case .success = await timelineController.sendLocation(body: geoURI.bodyMessage,
|
guard case .success = await timelineController.sendLocation(body: geoURI.bodyMessage,
|
||||||
geoURI: geoURI,
|
geoURI: geoURI,
|
||||||
@@ -157,6 +218,7 @@ extension LocationSharingScreenViewModel {
|
|||||||
liveLocationSharingEnabled: liveLocationSharingEnabled,
|
liveLocationSharingEnabled: liveLocationSharingEnabled,
|
||||||
roomProxy: JoinedRoomProxyMock(.init()),
|
roomProxy: JoinedRoomProxyMock(.init()),
|
||||||
timelineController: MockTimelineController(),
|
timelineController: MockTimelineController(),
|
||||||
|
liveLocationManager: LiveLocationManagerMock(),
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ struct LocationPickerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if context.viewState.showLiveLocationSharingButton {
|
if context.viewState.showLiveLocationSharingButton {
|
||||||
Button { } label: {
|
Button {
|
||||||
|
context.send(viewAction: .startLiveLocation)
|
||||||
|
} label: {
|
||||||
LocationPickerLabel(text: L10n.actionShareLiveLocation,
|
LocationPickerLabel(text: L10n.actionShareLiveLocation,
|
||||||
icon: \.locationPinSolid,
|
icon: \.locationPinSolid,
|
||||||
iconColor: .compound.iconAccentPrimary)
|
iconColor: .compound.iconAccentPrimary)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
liveLocationSharingEnabled: true,
|
liveLocationSharingEnabled: true,
|
||||||
roomProxy: JoinedRoomProxyMock(.init()),
|
roomProxy: JoinedRoomProxyMock(.init()),
|
||||||
timelineController: MockTimelineController(timelineProxy: timelineProxy),
|
timelineController: MockTimelineController(timelineProxy: timelineProxy),
|
||||||
|
liveLocationManager: LiveLocationManagerMock(),
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||||
|
|||||||
Reference in New Issue
Block a user