diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index d07f6efb5..7998caf91 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -15,6 +15,7 @@ // import Foundation +import Keys /// BuildSettings provides settings computed at build time. /// In future, it may be automatically generated from xcconfig files @@ -364,4 +365,8 @@ final class BuildSettings: NSObject { return true } + + // MARK: - Location Sharing + + static let tileServerMapURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=" + RiotKeys().mapTilerAPIKey)! } diff --git a/Riot/Assets/en.lproj/InfoPlist.strings b/Riot/Assets/en.lproj/InfoPlist.strings index e8ac01167..a9513a6ed 100644 --- a/Riot/Assets/en.lproj/InfoPlist.strings +++ b/Riot/Assets/en.lproj/InfoPlist.strings @@ -21,4 +21,4 @@ "NSContactsUsageDescription" = "Element will show your contacts so you can invite them to chat."; "NSCalendarsUsageDescription" = "See your scheduled meetings in the app."; "NSFaceIDUsageDescription" = "Face ID is used to access your app."; -"NSLocationWhenInUseUsageDescription" = "Element needs access to your location before being able to share it."; +"NSLocationWhenInUseUsageDescription" = "When you share your location to people, Element needs access to show them a map."; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index babe98289..51ed3037c 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -66,6 +66,7 @@ "less" = "Less"; "open" = "Open"; "done" = "Done"; +"ok" = "OK"; // Call Bar "callbar_only_single_active" = "Tap to return to the call (%@)"; @@ -1821,8 +1822,6 @@ Tap the + to start adding people."; "poll_edit_form_post_failure_subtitle" = "Please try again"; -"poll_edit_form_post_failure_action" = "OK"; - "poll_timeline_one_vote" = "1 vote"; "poll_timeline_votes_count" = "%lu votes"; @@ -1845,14 +1844,10 @@ Tap the + to start adding people."; "poll_timeline_vote_not_registered_subtitle" = "Sorry, your vote was not registered, please try again"; -"poll_timeline_vote_not_registered_action" = "OK"; - "poll_timeline_not_closed_title" = "Failed to end poll"; "poll_timeline_not_closed_subtitle" = "Please try again"; -"poll_timeline_not_closed_action" = "OK"; - // MARK: - Location sharing "location_sharing_title" = "Location"; @@ -1861,14 +1856,12 @@ Tap the + to start adding people."; "location_sharing_share_action" = "Share"; -"location_sharing_loading_map_error_title" = "Failed to load map"; +"location_sharing_loading_map_error_title" = "Element could not load the map. Please try again later."; -"location_sharing_loading_map_error_message" = "Please try again"; +"location_sharing_locating_user_error_title" = "Element could not access your location. Please try again later."; -"location_sharing_locating_user_error_title" = "Failed locating user"; +"location_sharing_invalid_authorization_error_title" = "Element does not have permission to access your location. You can enable access in Settings > Location"; -"location_sharing_locating_user_error_message" = "Please try again"; +"location_sharing_invalid_authorization_not_now" = "Not now"; -"location_sharing_invalid_authorization_error_title" = "Failed getting authorization"; - -"location_sharing_invalid_authorization_error_message" = "Please update your system settings"; +"location_sharing_invalid_authorization_settings" = "Settings"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 9fbe34e43..273a1b5be 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2195,27 +2195,23 @@ public class VectorL10n: NSObject { public static var locationSharingCloseAction: String { return VectorL10n.tr("Vector", "location_sharing_close_action") } - /// Please update your system settings - public static var locationSharingInvalidAuthorizationErrorMessage: String { - return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_error_message") - } - /// Failed getting authorization + /// Element does not have permission to access your location. You can enable access in Settings > Location public static var locationSharingInvalidAuthorizationErrorTitle: String { return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_error_title") } - /// Please try again - public static var locationSharingLoadingMapErrorMessage: String { - return VectorL10n.tr("Vector", "location_sharing_loading_map_error_message") + /// Not now + public static var locationSharingInvalidAuthorizationNotNow: String { + return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_not_now") } - /// Failed to load map + /// Settings + public static var locationSharingInvalidAuthorizationSettings: String { + return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_settings") + } + /// Element could not load the map. Please try again later. public static var locationSharingLoadingMapErrorTitle: String { return VectorL10n.tr("Vector", "location_sharing_loading_map_error_title") } - /// Please try again - public static var locationSharingLocatingUserErrorMessage: String { - return VectorL10n.tr("Vector", "location_sharing_locating_user_error_message") - } - /// Failed locating user + /// Element could not access your location. Please try again later. public static var locationSharingLocatingUserErrorTitle: String { return VectorL10n.tr("Vector", "location_sharing_locating_user_error_title") } @@ -2327,6 +2323,10 @@ public class VectorL10n: NSObject { public static var off: String { return VectorL10n.tr("Vector", "off") } + /// OK + public static var ok: String { + return VectorL10n.tr("Vector", "ok") + } /// On public static var on: String { return VectorL10n.tr("Vector", "on") @@ -2479,10 +2479,6 @@ public class VectorL10n: NSObject { public static var pollEditFormPollQuestionOrTopic: String { return VectorL10n.tr("Vector", "poll_edit_form_poll_question_or_topic") } - /// OK - public static var pollEditFormPostFailureAction: String { - return VectorL10n.tr("Vector", "poll_edit_form_post_failure_action") - } /// Please try again public static var pollEditFormPostFailureSubtitle: String { return VectorL10n.tr("Vector", "poll_edit_form_post_failure_subtitle") @@ -2495,10 +2491,6 @@ public class VectorL10n: NSObject { public static var pollEditFormQuestionOrTopic: String { return VectorL10n.tr("Vector", "poll_edit_form_question_or_topic") } - /// OK - public static var pollTimelineNotClosedAction: String { - return VectorL10n.tr("Vector", "poll_timeline_not_closed_action") - } /// Please try again public static var pollTimelineNotClosedSubtitle: String { return VectorL10n.tr("Vector", "poll_timeline_not_closed_subtitle") @@ -2539,10 +2531,6 @@ public class VectorL10n: NSObject { public static func pollTimelineTotalVotesNotVoted(_ p1: Int) -> String { return VectorL10n.tr("Vector", "poll_timeline_total_votes_not_voted", p1) } - /// OK - public static var pollTimelineVoteNotRegisteredAction: String { - return VectorL10n.tr("Vector", "poll_timeline_vote_not_registered_action") - } /// Sorry, your vote was not registered, please try again public static var pollTimelineVoteNotRegisteredSubtitle: String { return VectorL10n.tr("Vector", "poll_timeline_vote_not_registered_subtitle") diff --git a/Riot/Modules/Room/Location/RoomTimelineLocationView.swift b/Riot/Modules/Room/Location/RoomTimelineLocationView.swift index 75c6da792..4e945e168 100644 --- a/Riot/Modules/Room/Location/RoomTimelineLocationView.swift +++ b/Riot/Modules/Room/Location/RoomTimelineLocationView.swift @@ -25,7 +25,6 @@ class RoomTimelineLocationView: UIView, NibLoadable, MGLMapViewDelegate { static let mapHeight: CGFloat = 300.0 static let mapTilerKey = RiotKeys().mapTilerAPIKey static let mapZoomLevel = 15.0 - static let mapStyleURLString = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=\(Constants.mapTilerKey)") static let cellBorderRadius: CGFloat = 1.0 static let cellCornerRadius: CGFloat = 8.0 } @@ -54,7 +53,7 @@ class RoomTimelineLocationView: UIView, NibLoadable, MGLMapViewDelegate { override func awakeFromNib() { super.awakeFromNib() - mapView = MGLMapView(frame: .zero, styleURL: Constants.mapStyleURLString) + mapView = MGLMapView(frame: .zero, styleURL: BuildSettings.tileServerMapURL) mapView.delegate = self mapView.logoView.isHidden = true mapView.attributionButton.isHidden = true diff --git a/Riot/SupportingFiles/Info.plist b/Riot/SupportingFiles/Info.plist index 4c4d11e2f..9d95fe929 100644 --- a/Riot/SupportingFiles/Info.plist +++ b/Riot/SupportingFiles/Info.plist @@ -66,7 +66,7 @@ NSSiriUsageDescription Siri is used to perform calls even from the lock screen. NSLocationWhenInUseUsageDescription - Element needs access to your location before being able to share it. + When you share your location to people, Element needs access to show them a map. UIBackgroundModes audio diff --git a/RiotSwiftUI/Info.plist b/RiotSwiftUI/Info.plist index a74882893..c72d62e92 100644 --- a/RiotSwiftUI/Info.plist +++ b/RiotSwiftUI/Info.plist @@ -21,6 +21,6 @@ CFBundleVersion $(CURRENT_PROJECT_VERSION) NSLocationWhenInUseUsageDescription - Element needs access to your location before being able to share it. + When you share your location to people, Element needs access to show them a map. diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift b/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift index dc77cd6c3..bcff3fbca 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift @@ -56,7 +56,7 @@ final class LocationSharingCoordinator: Coordinator { matrixItemId: parameters.user.userId, displayName: parameters.user.displayname) - let viewModel = LocationSharingViewModel(accessToken: RiotKeys().mapTilerAPIKey, avatarData: avatarData) + let viewModel = LocationSharingViewModel(tileServerMapURL: BuildSettings.tileServerMapURL, avatarData: avatarData) let view = LocationSharingView(context: viewModel.context) .addDependency(AvatarService.instantiate(mediaManager: parameters.mediaManager)) diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift index 88310f4d1..f4ceb2941 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift @@ -46,7 +46,7 @@ enum LocationSharingViewModelResult { @available(iOS 14, *) struct LocationSharingViewState: BindableState { - let accessToken: String + let tileServerMapURL: URL let avatarData: AvatarInputProtocol var shareButtonEnabled: Bool = true var showLoadingIndicator: Bool = false @@ -71,6 +71,6 @@ struct ErrorAlertInfo: Identifiable { let id: AlertType let title: String - let message: String - let callback: (() -> Void)? + let primaryButton: (String, (() -> Void)?) + let secondaryButton: (String, (() -> Void)?)? } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift index 44699462b..01c77b09d 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift @@ -16,6 +16,7 @@ import Foundation import SwiftUI +import Keys @available(iOS 14.0, *) enum MockLocationSharingScreenState: MockScreenState, CaseIterable { @@ -26,7 +27,8 @@ enum MockLocationSharingScreenState: MockScreenState, CaseIterable { } var screenView: ([Any], AnyView) { - let viewModel = LocationSharingViewModel(accessToken: "bDAfUcrMPWTAB1KB38r6", + let mapURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=" + RiotKeys().mapTilerAPIKey)! + let viewModel = LocationSharingViewModel(tileServerMapURL: mapURL, avatarData: AvatarInput(mxContentUri: "", matrixItemId: "", displayName: "Alice")) return ([viewModel], AnyView(LocationSharingView(context: viewModel.context) diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift index b4b85f29c..b4dda5af3 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift @@ -30,18 +30,18 @@ class LocationSharingViewModel: LocationSharingViewModelType { // MARK: Public - let accessToken: String + let tileServerMapURL: URL let avatarData: AvatarInputProtocol var completion: ((LocationSharingViewModelResult) -> Void)? // MARK: - Setup - init(accessToken: String, avatarData: AvatarInputProtocol) { - self.accessToken = accessToken + init(tileServerMapURL: URL, avatarData: AvatarInputProtocol) { + self.tileServerMapURL = tileServerMapURL self.avatarData = avatarData - super.init(initialViewState: LocationSharingViewState(accessToken: accessToken, avatarData: avatarData)) + super.init(initialViewState: LocationSharingViewState(tileServerMapURL: tileServerMapURL, avatarData: avatarData)) state.errorSubject.sink { [weak self] error in guard let self = self else { return } @@ -69,26 +69,26 @@ class LocationSharingViewModel: LocationSharingViewModelType { switch action { case .error(let error, let completion): - let alertCallback: () -> Void = { - completion?(.cancel) - } - switch error { case .failedLoadingMap: state.bindings.alertInfo = ErrorAlertInfo(id: .mapLoadingError, title: VectorL10n.locationSharingLoadingMapErrorTitle, - message: VectorL10n.locationSharingLoadingMapErrorMessage, - callback: alertCallback) + primaryButton: (VectorL10n.ok, { completion?(.cancel) }), + secondaryButton: nil) case .failedLocatingUser: state.bindings.alertInfo = ErrorAlertInfo(id: .userLocatingError, title: VectorL10n.locationSharingLocatingUserErrorTitle, - message: VectorL10n.locationSharingLocatingUserErrorMessage, - callback: alertCallback) + primaryButton: (VectorL10n.ok, { completion?(.cancel) }), + secondaryButton: nil) case .invalidLocationAuthorization: state.bindings.alertInfo = ErrorAlertInfo(id: .authorizationError, title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle, - message: VectorL10n.locationSharingInvalidAuthorizationErrorMessage, - callback: alertCallback) + primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, { completion?(.cancel) }), + secondaryButton: (VectorL10n.locationSharingInvalidAuthorizationSettings, { + if let applicationSettingsURL = URL(string:UIApplication.openSettingsURLString) { + UIApplication.shared.open(applicationSettingsURL) + } + })) default: break } @@ -103,8 +103,8 @@ class LocationSharingViewModel: LocationSharingViewModelType { if error != nil { state.bindings.alertInfo = ErrorAlertInfo(id: .locationSharingError, title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle, - message: VectorL10n.locationSharingInvalidAuthorizationErrorMessage, - callback: nil) + primaryButton: (VectorL10n.ok, nil), + secondaryButton: nil) } } } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift b/RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift index 702b336be..dc51dd83e 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift @@ -26,7 +26,7 @@ class LocationSharingViewModelTests: XCTestCase { var cancellables = Set() override func setUpWithError() throws { - context = viewModel.context + } func testInitialState() { diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift index aa515338d..edbeb4298 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift @@ -22,18 +22,15 @@ import Mapbox struct LocationSharingMapView: UIViewRepresentable { private struct Constants { static let mapZoomLevel = 15.0 - static let mapStyleURLString = "https://api.maptiler.com/maps/streets/style.json?key=" } - let accessToken: String + let tileServerMapURL: URL let avatarData: AvatarInputProtocol let errorSubject: PassthroughSubject @Binding var userLocation: CLLocationCoordinate2D? func makeUIView(context: Context) -> some UIView { - let url = URL(string: Constants.mapStyleURLString + accessToken) - - let mapView = MGLMapView(frame: .zero, styleURL: url) + let mapView = MGLMapView(frame: .zero, styleURL: tileServerMapURL) mapView.delegate = context.coordinator mapView.logoView.isHidden = true diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift index f760c7dc3..caa4e5846 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift @@ -32,7 +32,7 @@ struct LocationSharingView: View { var body: some View { NavigationView { - LocationSharingMapView(accessToken: context.viewState.accessToken, + LocationSharingMapView(tileServerMapURL: context.viewState.tileServerMapURL, avatarData: context.viewState.avatarData, errorSubject: context.viewState.errorSubject, userLocation: $context.userLocation) @@ -57,10 +57,20 @@ struct LocationSharingView: View { .navigationBarTitleDisplayMode(.inline) .ignoresSafeArea() .alert(item: $context.alertInfo) { info in - Alert(title: Text(info.title), message: Text(info.message), dismissButton: - .default(Text(VectorL10n.pollTimelineVoteNotRegisteredAction)) { - info.callback?() - }) + if let secondaryButton = info.secondaryButton { + return Alert(title: Text(info.title), + primaryButton: .default(Text(info.primaryButton.0)) { + info.primaryButton.1?() + }, + secondaryButton: .default(Text(secondaryButton.0)) { + secondaryButton.1?() + }) + } else { + return Alert(title: Text(info.title), + dismissButton: .default(Text(info.primaryButton.0)) { + info.primaryButton.1?() + }) + } } } .accentColor(theme.colors.accent) diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift index 9e81488a5..57f3a63a3 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift @@ -87,7 +87,7 @@ struct PollEditForm: View { .alert(isPresented: $viewModel.showsFailureAlert) { Alert(title: Text(VectorL10n.pollEditFormPostFailureTitle), message: Text(VectorL10n.pollEditFormPostFailureSubtitle), - dismissButton: .default(Text(VectorL10n.pollEditFormPostFailureAction))) + dismissButton: .default(Text(VectorL10n.ok))) } .frame(minHeight: proxy.size.height) // Make the VStack fill the ScrollView's parent .toolbar { diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineView.swift b/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineView.swift index 22a94bb68..70efcc067 100644 --- a/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineView.swift +++ b/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineView.swift @@ -50,7 +50,7 @@ struct PollTimelineView: View { .alert(isPresented: $viewModel.showsClosingFailureAlert) { Alert(title: Text(VectorL10n.pollTimelineNotClosedTitle), message: Text(VectorL10n.pollTimelineNotClosedSubtitle), - dismissButton: .default(Text(VectorL10n.pollTimelineNotClosedAction))) + dismissButton: .default(Text(VectorL10n.ok))) } } .disabled(poll.closed) @@ -62,7 +62,7 @@ struct PollTimelineView: View { .alert(isPresented: $viewModel.showsAnsweringFailureAlert) { Alert(title: Text(VectorL10n.pollTimelineVoteNotRegisteredTitle), message: Text(VectorL10n.pollTimelineVoteNotRegisteredSubtitle), - dismissButton: .default(Text(VectorL10n.pollTimelineVoteNotRegisteredAction))) + dismissButton: .default(Text(VectorL10n.ok))) } } .padding([.horizontal, .top], 2.0)