Show alert if user has not beacon and beaconInfo permissions in the room.

This commit is contained in:
Mauro Romito
2026-05-05 13:18:37 +02:00
committed by Mauro
parent fe6c62b60f
commit 0a896e5ba2
6 changed files with 91 additions and 57 deletions

View File

@@ -1408,6 +1408,7 @@
"screen_session_verification_waiting_to_accept_title" = "Waiting to accept request";
"screen_share_location_live_location_disclaimer_title" = "Your live location history will be stored in the room and visible to members after the session ends.";
"screen_share_location_live_location_duration_picker_title" = "Choose how long to share your live location.";
"screen_share_location_live_location_missing_permissions" = "You do not have permissions to share your live location in this room";
"screen_share_location_title" = "Share location";
"screen_share_my_location_action" = "Share my location";
"screen_share_open_apple_maps" = "Open in Apple Maps";

View File

@@ -1408,6 +1408,7 @@
"screen_session_verification_waiting_to_accept_title" = "Waiting to accept request";
"screen_share_location_live_location_disclaimer_title" = "Your live location history will be stored in the room and visible to members after the session ends.";
"screen_share_location_live_location_duration_picker_title" = "Choose how long to share your live location.";
"screen_share_location_live_location_missing_permissions" = "You do not have permissions to share your live location in this room";
"screen_share_location_title" = "Share location";
"screen_share_my_location_action" = "Share my location";
"screen_share_open_apple_maps" = "Open in Apple Maps";

View File

@@ -3265,6 +3265,8 @@ internal enum L10n {
internal static var screenShareLocationLiveLocationDisclaimerTitle: String { return L10n.tr("Localizable", "screen_share_location_live_location_disclaimer_title") }
/// Choose how long to share your live location.
internal static var screenShareLocationLiveLocationDurationPickerTitle: String { return L10n.tr("Localizable", "screen_share_location_live_location_duration_picker_title") }
/// You do not have permissions to share your live location in this room
internal static var screenShareLocationLiveLocationMissingPermissions: String { return L10n.tr("Localizable", "screen_share_location_live_location_missing_permissions") }
/// Share location
internal static var screenShareLocationTitle: String { return L10n.tr("Localizable", "screen_share_location_title") }
/// Share my location

View File

@@ -13,6 +13,7 @@ import MatrixRustSDK
enum LocationSharingViewAlert: Hashable {
case missingAuthorization
case missingAlwaysAuthorization
case missingLiveLocationSharingPermission
case liveLocationDisclaimer
case liveLocationDurationSelection
case mapError(MapLibreError)
@@ -213,6 +214,10 @@ extension AlertInfo where T == LocationSharingViewAlert {
message: L10n.dialogPermissionLiveLocationDescriptionIos(InfoPlistReader.main.bundleDisplayName),
primaryButton: primaryButton,
secondaryButton: secondaryButton)
case .missingLiveLocationSharingPermission:
self.init(id: alertID,
title: L10n.screenShareLocationLiveLocationMissingPermissions,
primaryButton: primaryButton)
case .liveLocationDisclaimer:
self.init(id: alertID,
title: L10n.screenShareLocationLiveLocationDisclaimerTitle,

View File

@@ -68,7 +68,7 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
case .close:
actionsSubject.send(.close)
case .startLiveLocation:
checkAlwaysShareLocationPermission()
startLiveLocation()
case .selectLocation:
guard let coordinate = state.bindings.mapCenterLocation else { return }
let uncertainty = state.isSharingUserLocation ? context.geolocationUncertainty : nil
@@ -171,12 +171,16 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
}
}
private static let durationFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.hour, .minute]
return formatter
}()
private func startLiveLocation() {
guard let powerLevels = roomProxy.infoPublisher.value.powerLevels,
powerLevels.canOwnUser(sendStateEvent: .beaconInfo),
powerLevels.canOwnUser(sendMessage: .beacon) else {
state.bindings.alertInfo = .init(alertID: .missingLiveLocationSharingPermission)
return
}
checkAlwaysShareLocationPermission()
}
private func checkAlwaysShareLocationPermission() {
authorizationStatusSubscription = nil
@@ -231,6 +235,15 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
}
}
/// It's easier to achieve the format we want with a DateComponentsFormatter
/// than using the `.formatted` function of Duration.
private static let durationFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.hour, .minute]
return formatter
}()
private func showLiveLocationDurationPicker() {
let durations: [Duration] = [.seconds(15 * 60), // 15 minutes
.seconds(60 * 60), // 1 hour

View File

@@ -172,6 +172,16 @@ struct LocationSharingScreenViewModelTests {
#expect(context.viewState.isLocationLoading)
}
// MARK: - Live Location Permission Tests
@Test
mutating func startLiveLocationWithoutPermission() {
setupViewModel(liveLocationManagerConfiguration: .init(authorizationStatus: .authorizedAlways),
members: .allMembers)
context.send(viewAction: .startLiveLocation)
#expect(context.alertInfo?.id == .missingLiveLocationSharingPermission)
}
// MARK: - Live Location Authorization Tests
@Test
@@ -415,11 +425,12 @@ struct LocationSharingScreenViewModelTests {
// MARK: - Private
private mutating func setupViewModel(liveLocationManagerConfiguration: LiveLocationManagerMock.Configuration = .init()) {
private mutating func setupViewModel(liveLocationManagerConfiguration: LiveLocationManagerMock.Configuration = .init(),
members: [RoomMemberProxyMock] = .allMembersAsAdmin) {
timelineProxy = TimelineProxyMock(.init())
viewModel = LocationSharingScreenViewModel(interactionMode: .picker,
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
roomProxy: JoinedRoomProxyMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(members: members)),
timelineController: MockTimelineController(timelineProxy: timelineProxy),
liveLocationManager: LiveLocationManagerMock(liveLocationManagerConfiguration),
analytics: ServiceLocator.shared.analytics,
@@ -428,11 +439,12 @@ struct LocationSharingScreenViewModelTests {
viewModel.state.bindings.isLocationAuthorized = true
}
private mutating func setupViewModel(liveLocationManagerMock: LiveLocationManagerMock) {
private mutating func setupViewModel(liveLocationManagerMock: LiveLocationManagerMock,
members: [RoomMemberProxyMock] = .allMembersAsAdmin) {
timelineProxy = TimelineProxyMock(.init())
viewModel = LocationSharingScreenViewModel(interactionMode: .picker,
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
roomProxy: JoinedRoomProxyMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(members: members)),
timelineController: MockTimelineController(timelineProxy: timelineProxy),
liveLocationManager: liveLocationManagerMock,
analytics: ServiceLocator.shared.analytics,