vector-im/element-ios/issues/5298 - Improved error messages and moved map configuration to the BuildSettings

This commit is contained in:
Stefan Ceriu 2021-12-17 14:27:43 +02:00 committed by Stefan Ceriu
parent f0cdbe5a7c
commit 352d56726e
16 changed files with 73 additions and 79 deletions

View file

@ -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)!
}

View file

@ -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.";

View file

@ -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";

View file

@ -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")

View file

@ -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

View file

@ -66,7 +66,7 @@
<key>NSSiriUsageDescription</key>
<string>Siri is used to perform calls even from the lock screen.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Element needs access to your location before being able to share it.</string>
<string>When you share your location to people, Element needs access to show them a map.</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>

View file

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Element needs access to your location before being able to share it.</string>
<string>When you share your location to people, Element needs access to show them a map.</string>
</dict>
</plist>

View file

@ -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))

View file

@ -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)?)?
}

View file

@ -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)

View file

@ -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)
}
}
}

View file

@ -26,7 +26,7 @@ class LocationSharingViewModelTests: XCTestCase {
var cancellables = Set<AnyCancellable>()
override func setUpWithError() throws {
context = viewModel.context
}
func testInitialState() {

View file

@ -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<LocationSharingViewError, Never>
@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

View file

@ -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)

View file

@ -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 {

View file

@ -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)