diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..f4ed7ca701a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.strings diff diff --git a/.tx/config b/.tx/config index 1a6963888fd..10829d5934e 100644 --- a/.tx/config +++ b/.tx/config @@ -5,19 +5,19 @@ minimum_perc = 80 type = STRINGS [mapbox-navigation-ios.LocalizableStrings] -file_filter = MapboxNavigation/Resources/.lproj/Localizable.strings -source_file = MapboxNavigation/Resources/Base.lproj/Localizable.strings +file_filter = Sources/MapboxNavigation/Resources/.lproj/Localizable.strings +source_file = Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings source_lang = en [mapbox-navigation-ios.localizablestringsdict] -file_filter = MapboxNavigation/Resources/.lproj/Localizable.stringsdict -source_file = MapboxNavigation/Resources/en.lproj/Localizable.stringsdict +file_filter = Sources/MapboxNavigation/Resources/.lproj/Localizable.stringsdict +source_file = Sources/MapboxNavigation/Resources/en.lproj/Localizable.stringsdict source_lang = en type = STRINGSDICT [mapbox-navigation-ios.LocalizableCoreStrings] -file_filter = MapboxCoreNavigation/Resources/.lproj/Localizable.strings -source_file = MapboxCoreNavigation/Resources/Base.lproj/Localizable.strings +file_filter = Sources/MapboxCoreNavigation/Resources/.lproj/Localizable.strings +source_file = Sources/MapboxCoreNavigation/Resources/Base.lproj/Localizable.strings source_lang = en [mapbox-navigation-ios.ExampleMainStrings] @@ -26,6 +26,6 @@ source_file = Example/en.lproj/Main.strings source_lang = en [mapbox-navigation-ios.navigationstrings] -file_filter = MapboxNavigation/Resources/.lproj/Navigation.strings -source_file = MapboxNavigation/Resources/Base.lproj/Navigation.strings +file_filter = Sources/MapboxNavigation/Resources/.lproj/Navigation.strings +source_file = Sources/MapboxNavigation/Resources/Base.lproj/Navigation.strings source_lang = en diff --git a/Bench/Assets.xcassets/Contents.json b/Bench/Assets.xcassets/Contents.json index da4a164c918..73c00596a7f 100644 --- a/Bench/Assets.xcassets/Contents.json +++ b/Bench/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index c905df13d71..4f49a5f0755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,15 +76,28 @@ * The duration annotations added by the `NavigationMapView.showRouteDurations(along:)` method are now set in the fonts you specify using the `NavigationMapView.routeDurationAnnotationFontNames` property. Use this property to specify a list of fallback fonts for better language support. ([#2873](https://github.com/mapbox/mapbox-navigation-ios/pull/2873)) * Removed deprecated and obsoleted `EventsManager` typealias, `StatusView.delegate`, `StatusView.canChangeValue`, `RouteLegProgress.upComingStep`, `StatusViewDelegate`, `DeprecatedStatusViewDelegate` and `BottomBannerViewController.init(delegate:)`. ([#2993](https://github.com/mapbox/mapbox-navigation-ios/pull/2993)) +## v1.4.0 + +* Increased the minimum version of `MapboxNavigationNative` to v32.0.0. ([#2910](https://github.com/mapbox/mapbox-navigation-ios/pull/2910)) +* Fixed an issue when feedback UI was always appearing for short routes. ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) +* Fixed automatic day/night switching. ([#2881](https://github.com/mapbox/mapbox-navigation-ios/pull/2881)) +* Fixed an issue where presenting `NavigationViewController` could sometimes interfere with view presentation in other windows. ([#2897](https://github.com/mapbox/mapbox-navigation-ios/pull/2897)) +* Added an optional `StyleManagerDelegate.styleManager(_:viewForApplying:)` method to determine which part of the view hierarchy is affected by a change to a different `Style`. ([#2897](https://github.com/mapbox/mapbox-navigation-ios/pull/2897)) +* Added the `NavigationViewController.styleManager`, `StyleManager.currentStyleType`, and `StyleManager.currentStyle` properties and the `StyleManager.applyStyle(type:)` method to manually change the UI style at any time. ([#2888](https://github.com/mapbox/mapbox-navigation-ios/pull/2888)) +* Fixed crash when switching between day / night modes. ([#2896](https://github.com/mapbox/mapbox-navigation-ios/pull/2896)) +* Added support for iOS 13's UIScene based CarPlay API. ([#2832](https://github.com/mapbox/mapbox-navigation-ios/pull/2832)) +* Added an optional `CarPlayManagerDelegate.carPlayManager(_:didPresent:)` method that is called when `CarPlayManager` presents a new navigation session. ([#2832](https://github.com/mapbox/mapbox-navigation-ios/pull/2832)) + ## v1.3.0 * MapboxCoreNavigation can now be installed using Swift Package Manager. ([#2771](https://github.com/mapbox/mapbox-navigation-ios/pull/2771)) -* The CarPlay guidance panel now shows lane guidance. ([#2798](https://github.com/mapbox/mapbox-navigation-ios/pull/2798)) +* The CarPlay guidance panel now shows lane guidance. ([#1885](https://github.com/mapbox/mapbox-navigation-ios/pull/1885)) * Old versions of routing tiles are automatically deleted from the cache to save storage space. ([#2807](https://github.com/mapbox/mapbox-navigation-ios/pull/2807)) * Fixed an issue where lane guidance icons would indicate the wrong arrow for certain maneuvers. ([#2796](https://github.com/mapbox/mapbox-navigation-ios/pull/2796), [#2809](https://github.com/mapbox/mapbox-navigation-ios/pull/2809)) * Fixed a crash showing a junction view. ([#2805](https://github.com/mapbox/mapbox-navigation-ios/pull/2805)) * Fixed an issue with CarPlay visual instructions where U-Turn maneuver icons were not being flipped properly based on regional driving side ([#2803](https://github.com/mapbox/mapbox-navigation-ios/pull/2803)) * Fixed swiping for right-to-left languages for the Guidance Card UI to be more intuitive. ([#2724](https://github.com/mapbox/mapbox-navigation-ios/pull/2724)) +* `NavigationViewController` can now manage multiple status banners one after another. Renamed the `NavigationViewController.showStatus(title:spinner:duration:animated:interactive:)` method to `NavigationViewController.show(_:)` and added a corresponding `NavigationViewController.hide(_:)` method. Renamed the `NavigationStatusPresenter.showStatus(title:spinner:duration:animated:interactive:)` method to `NavigationStatusPresenter.show(_:)` and added a `NavigationStatusPresenter.hide(_:)` method. ([#2747](https://github.com/mapbox/mapbox-navigation-ios/pull/2747)) ## v1.2.1 diff --git a/Cartfile.private b/Cartfile.private index 72342261b97..98ed1273b71 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,3 +1,3 @@ github "mapbox/MapboxGeocoder.swift" ~> 0.10 -github "Quick/Quick" ~> 2.0 -github "Quick/Nimble" ~> 8.0 +github "Quick/Quick" ~> 3.1.2 +github "Quick/Nimble" ~> 9.0.1 \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved index 665575dd536..45ef751a1b0 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ binary "https://api.mapbox.com/downloads/v2/carthage/mapbox-common/MapboxCommon-ios.json" "11.0.2" binary "https://api.mapbox.com/downloads/v2/carthage/mobile-navigation-native/MapboxNavigationNative.json" "48.0.5" -github "Quick/Nimble" "v8.1.2" -github "Quick/Quick" "v2.2.1" +github "Quick/Nimble" "v9.2.0" +github "Quick/Quick" "v3.1.2" github "Udumft/SwiftCLI" "da19d2a16cd5aa838d8fb7256e28c171bc67dd82" github "mapbox/MapboxGeocoder.swift" "v0.10.2" github "mapbox/mapbox-directions-swift" "v2.0.0-beta.3" diff --git a/Example/AppDelegate+CarPlay.swift b/Example/AppDelegate+CarPlay.swift index dfda6180348..67a38a7c57a 100644 --- a/Example/AppDelegate+CarPlay.swift +++ b/Example/AppDelegate+CarPlay.swift @@ -1,6 +1,5 @@ import UIKit import MapboxNavigation -#if canImport(CarPlay) import CarPlay import MapboxCoreNavigation import MapboxDirections @@ -149,6 +148,10 @@ extension AppDelegate: CarPlayManagerDelegate { return nil } } + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + // no-op + } } @available(iOS 12.0, *) @@ -186,4 +189,23 @@ extension AppDelegate: CPListTemplateDelegate { completionHandler() } } -#endif + +@available(iOS 13.0, *) +class CarPlaySceneDelegate: NSObject, CPTemplateApplicationSceneDelegate { + + func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, + didConnect interfaceController: CPInterfaceController, to window: CPWindow) { + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } + appDelegate.carPlayManager.delegate = appDelegate + appDelegate.carPlaySearchController.delegate = appDelegate + appDelegate.carPlayManager.templateApplicationScene(templateApplicationScene, didConnectCarInterfaceController: interfaceController, to: window) + } + + func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, + didDisconnect interfaceController: CPInterfaceController, from window: CPWindow) { + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } + appDelegate.carPlayManager.templateApplicationScene(templateApplicationScene, didDisconnectCarInterfaceController: interfaceController, from: window) + } +} diff --git a/Example/AppDelegate.swift b/Example/AppDelegate.swift index 4501ee6d787..d07f984592a 100644 --- a/Example/AppDelegate.swift +++ b/Example/AppDelegate.swift @@ -1,5 +1,6 @@ import UIKit import MapboxNavigation +import CarPlay @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -14,6 +15,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if isRunningTests() { + if window == nil { + window = UIWindow(frame: UIScreen.main.bounds) + } window!.rootViewController = UIViewController() } @@ -37,3 +41,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } } + +@available(iOS 13.0, *) +extension AppDelegate: UIWindowSceneDelegate { + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + + if connectingSceneSession.role == .carTemplateApplication { + return UISceneConfiguration(name: "ExampleCarPlayApplicationConfiguration", sessionRole: connectingSceneSession.role) + } + return UISceneConfiguration(name: "ExampleAppConfiguration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + + } +} diff --git a/Example/Assets.xcassets/Contents.json b/Example/Assets.xcassets/Contents.json index da4a164c918..73c00596a7f 100644 --- a/Example/Assets.xcassets/Contents.json +++ b/Example/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Example/FavoritesList.swift b/Example/FavoritesList.swift index 3e811319f2e..a9c171f18f3 100644 --- a/Example/FavoritesList.swift +++ b/Example/FavoritesList.swift @@ -1,6 +1,4 @@ -#if canImport(CarPlay) import CarPlay -#endif import CoreLocation public enum FavoritesList { @@ -48,11 +46,9 @@ public enum FavoritesList { } } - #if canImport(CarPlay) @available(iOS 12.0, *) func listItem() -> CPListItem { return CPListItem(text: rawValue, detailText: subTitle, image: nil, showsDisclosureIndicator: true) } - #endif } } diff --git a/Example/Info.plist b/Example/Info.plist index 2a6b77766c6..40ccb7370d6 100644 --- a/Example/Info.plist +++ b/Example/Info.plist @@ -32,6 +32,38 @@ Get user location NSLocationWhenInUseUsageDescription Get user location + UIApplicationSceneManifest + + CPSupportsDashboardNavigationScene + + UISceneConfigurations + + CPTemplateApplicationSceneSessionRoleApplication + + + UISceneClassName + CPTemplateApplicationScene + UISceneConfigurationName + ExampleCarPlayApplicationConfiguration + UISceneDelegateClassName + Example.CarPlaySceneDelegate + + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + ExampleAppConfiguration + UISceneDelegateClassName + $(EXECUTABLE_NAME).AppDelegate + UISceneStoryboardFile + Main + + + + UIBackgroundModes audio diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index 923c047766a..0425597efc6 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -888,7 +888,7 @@ C53C19751F38EADE008DB406 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C53C19771F38EAE4008DB406 /* ca */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; C53C197A1F38EAEA008DB406 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; - C53F2F0720EBC95600D9798F /* Example-CarPlay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-CarPlay.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + C53F2F0720EBC95600D9798F /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; C549F8311F17F2C5001A0A2D /* MapboxMobileEvents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapboxMobileEvents.framework; path = Carthage/Build/iOS/MapboxMobileEvents.framework; sourceTree = ""; }; C54C655120336F2600D338E0 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; C551B0E520D42222009A986F /* NavigationLocationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationLocationManagerTests.swift; sourceTree = ""; }; @@ -928,6 +928,7 @@ DA0557242155040700A1F2AA /* RouteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteTests.swift; sourceTree = ""; }; DA0A4B7724D0BC7000D6B4F8 /* MapboxCommon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapboxCommon.framework; path = Carthage/Build/iOS/MapboxCommon.framework; sourceTree = ""; }; DA0A4B8024D1C9B200D6B4F8 /* Navigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigator.swift; sourceTree = ""; }; + DA0DBA2125FC4CC200E86BDF /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ca; path = Resources/ca.lproj/Localizable.stringsdict; sourceTree = ""; }; DA1811FE20128B0900C91918 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Navigation.strings; sourceTree = ""; }; DA18120120128B7B00C91918 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; DA18120320128E9400C91918 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -1298,6 +1299,7 @@ 35B711D01E5E7AD2001EDA8D /* MapboxNavigationTests */ = { isa = PBXGroup; children = ( + C3D225502587F411007DBCDF /* StatusViewTests.swift */, 16E11B53212B3D4700027CD3 /* Extensions and Categories */, 355DB5731EFA73410091BFB7 /* Fixtures */, 3527D2B61EC45FBD00C07FC9 /* Fixtures.xcassets */, @@ -1785,7 +1787,7 @@ 351BEBD71E5BCC28006FE110 /* MapboxNavigation.framework */, 358D14631E5E3B7700ADE590 /* Example.app */, 35B711CF1E5E7AD2001EDA8D /* MapboxNavigationTests.xctest */, - C53F2F0720EBC95600D9798F /* Example-CarPlay.app */, + C53F2F0720EBC95600D9798F /* Example.app */, 35CDA80621908F2F0072B675 /* Bench.app */, 35CDA81921908F320072B675 /* BenchTests.xctest */, 35CDA85E2190F2A30072B675 /* TestHelper.framework */, @@ -2074,7 +2076,7 @@ DA2157C425E093920086B294 /* MapboxNavigation */, ); productName = "Example-Swift"; - productReference = C53F2F0720EBC95600D9798F /* Example-CarPlay.app */; + productReference = C53F2F0720EBC95600D9798F /* Example.app */; productType = "com.apple.product-type.application"; }; C5ADFBC81DDCC7840011824B /* MapboxCoreNavigation */ = { @@ -2674,6 +2676,7 @@ 8D9CD7FF20880581004DC4B3 /* XCTestCase.swift in Sources */, 35DC585D1FABC61100B5A956 /* InstructionsBannerViewIntegrationTests.swift in Sources */, 3502231A205BC94E00E1449A /* Constants.swift in Sources */, + C3D225512587F411007DBCDF /* StatusViewTests.swift in Sources */, DA0557252155040700A1F2AA /* RouteTests.swift in Sources */, C55C299920D2E2F600B0406C /* NavigationMapViewTests.swift in Sources */, 4341758423061666004264A9 /* SnapshotTest+Mapbox.swift in Sources */, @@ -3089,6 +3092,7 @@ 354691B222C0D97000626C4F /* yo */, 8B808F982487D2BE00EEE453 /* el */, DA6C925924C60A43003A0AD6 /* tr */, + DA0DBA2125FC4CC200E86BDF /* ca */, ); name = Localizable.stringsdict; sourceTree = ""; @@ -3486,7 +3490,7 @@ "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.Mapbox.CarPlay-Example"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Example; PROVISIONING_PROFILE = "69c90fd8-c53b-41a4-ac73-5bc11068a49a"; PROVISIONING_PROFILE_SPECIFIER = "CarPlay Provisioning Profile"; SWIFT_OBJC_BRIDGING_HEADER = "Example/Example-Swift-BridgingHeader.h"; @@ -3512,7 +3516,7 @@ "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.Mapbox.CarPlay-Example"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Example; PROVISIONING_PROFILE = "69c90fd8-c53b-41a4-ac73-5bc11068a49a"; PROVISIONING_PROFILE_SPECIFIER = "CarPlay Provisioning Profile"; SWIFT_OBJC_BRIDGING_HEADER = "Example/Example-Swift-BridgingHeader.h"; diff --git a/MapboxNavigation.xcodeproj/xcshareddata/xcschemes/Example-CarPlay.xcscheme b/MapboxNavigation.xcodeproj/xcshareddata/xcschemes/Example-CarPlay.xcscheme index 58093514483..1a4b7323688 100644 --- a/MapboxNavigation.xcodeproj/xcshareddata/xcschemes/Example-CarPlay.xcscheme +++ b/MapboxNavigation.xcodeproj/xcshareddata/xcschemes/Example-CarPlay.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/README.md b/README.md index 71c31882ce0..1c336065a85 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ To install the MapboxNavigation framework using [CocoaPods](https://cocoapods.or 1. Create a [Podfile](https://guides.cocoapods.org/syntax/podfile.html) with the following specification: ```ruby # Latest stable release - pod 'MapboxNavigation', '~> 1.2' + pod 'MapboxNavigation', '~> 1.4' # Latest prerelease pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.8' pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.8' @@ -98,7 +98,7 @@ To install the MapboxNavigation framework using [Carthage](https://github.com/Ca 1. Create a [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#github-repositories) with the following dependency: ```cartfile # Latest stable release - github "mapbox/mapbox-navigation-ios" ~> 1.2 + github "mapbox/mapbox-navigation-ios" ~> 1.4 ``` 1. Run `./Carthage/Checkouts/mapbox-navigation-ios/scripts/wcarthage.sh bootstrap --platform iOS --cache-builds --use-netrc`. (wcarthage.sh is a temporary replacement for `carthage` to work around [a linker error in Xcode 12](https://github.com/Carthage/Carthage/issues/3019).) diff --git a/Sources/MapboxCoreNavigation/Resources/ca.lproj/Localizable.strings b/Sources/MapboxCoreNavigation/Resources/ca.lproj/Localizable.strings index 1728c7d2150..f39f758231c 100644 --- a/Sources/MapboxCoreNavigation/Resources/ca.lproj/Localizable.strings +++ b/Sources/MapboxCoreNavigation/Resources/ca.lproj/Localizable.strings @@ -1,5 +1,6 @@ -/* Format for speech string after completing a maneuver and starting a new step; 1 = distance */ -"CONTINUE" = "Continueu per %@"; +/* Error message when an offline route request returns a response that can’t be deserialized */ +"OFFLINE_CORRUPT_DATA" = "S'ha trobat una ruta no vàlida mentre fora de línia."; + +/* Inform developer an update is available */ +"UPDATE_AVAILABLE" = "Disponible la versió %@ de Mapbox Navigation SDK per iOS"; -/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ -"CONTINUE_ON_ROAD" = "Continueu a %1$@ per %2$@"; diff --git a/Sources/MapboxNavigation/CPMapTemplate.swift b/Sources/MapboxNavigation/CPMapTemplate.swift index 1c638f2b27a..bb80f163eb3 100644 --- a/Sources/MapboxNavigation/CPMapTemplate.swift +++ b/Sources/MapboxNavigation/CPMapTemplate.swift @@ -1,6 +1,5 @@ import Foundation import MapboxDirections -#if canImport(CarPlay) import CarPlay @available(iOS 12.0, *) @@ -32,5 +31,4 @@ extension CLLocationDirection { self = heading.wrap(min: 0, max: 360) } } -#endif diff --git a/Sources/MapboxNavigation/CPTrip.swift b/Sources/MapboxNavigation/CPTrip.swift index 6a4140cfada..95c0901e37a 100644 --- a/Sources/MapboxNavigation/CPTrip.swift +++ b/Sources/MapboxNavigation/CPTrip.swift @@ -1,37 +1,14 @@ import MapboxDirections -#if canImport(CarPlay) import CarPlay -#endif @available(iOS 12.0, *) extension CPTrip { - static let fullDateComponentsFormatter: DateComponentsFormatter = { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .full - formatter.allowedUnits = [.day, .hour, .minute] - return formatter - }() - - static let shortDateComponentsFormatter: DateComponentsFormatter = { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .short - formatter.allowedUnits = [.day, .hour, .minute] - return formatter - }() - - static let briefDateComponentsFormatter: DateComponentsFormatter = { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .brief - formatter.allowedUnits = [.day, .hour, .minute] - return formatter - }() - convenience init(routes: [Route], routeOptions: RouteOptions, waypoints: [Waypoint]) { let routeChoices = routes.enumerated().map { (routeIndex, route) -> CPRouteChoice in let summaryVariants = [ - CPTrip.fullDateComponentsFormatter.string(from: route.expectedTravelTime)!, - CPTrip.shortDateComponentsFormatter.string(from: route.expectedTravelTime)!, - CPTrip.briefDateComponentsFormatter.string(from: route.expectedTravelTime)! + DateComponentsFormatter.fullDateComponentsFormatter.string(from: route.expectedTravelTime)!, + DateComponentsFormatter.shortDateComponentsFormatter.string(from: route.expectedTravelTime)!, + DateComponentsFormatter.briefDateComponentsFormatter.string(from: route.expectedTravelTime)! ] let routeChoice = CPRouteChoice(summaryVariants: summaryVariants, additionalInformationVariants: [route.description], diff --git a/Sources/MapboxNavigation/CarPlayManager.swift b/Sources/MapboxNavigation/CarPlayManager.swift index 7a30ae0b6f6..26a183efcc5 100644 --- a/Sources/MapboxNavigation/CarPlayManager.swift +++ b/Sources/MapboxNavigation/CarPlayManager.swift @@ -1,4 +1,3 @@ -#if canImport(CarPlay) import CarPlay import MapboxCoreNavigation import MapboxDirections @@ -492,6 +491,7 @@ extension CarPlayManager: CPMapTemplateDelegate { carPlayMapViewController.present(navigationViewController, animated: true) { [weak self] in guard let self = self else { return } self.delegate?.carPlayManager(self, didBeginNavigationWith: service) + self.delegate?.carPlayManager(self, didPresent: navigationViewController) } let navigationMapView = carPlayMapViewController.navigationMapView @@ -671,7 +671,7 @@ extension CarPlayManager: CarPlayNavigationDelegate { interfaceController.setRootTemplate(mapTemplate, animated: true) popToRootTemplate(interfaceController: interfaceController, animated: true) - + delegate?.carPlayManagerDidEndNavigation(self) } } @@ -687,6 +687,51 @@ extension CarPlayManager: MapTemplateProviderDelegate { } } +@available(iOS 13.0, *) +extension CarPlayManager { + public func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnectCarInterfaceController interfaceController: CPInterfaceController, to window: CPWindow) { + CarPlayManager.isConnected = true + interfaceController.delegate = self + self.interfaceController = interfaceController + + if let shouldDisableIdleTimer = delegate?.carplayManagerShouldDisableIdleTimer(self) { + UIApplication.shared.isIdleTimerDisabled = shouldDisableIdleTimer + } else { + UIApplication.shared.isIdleTimerDisabled = true + } + + let carPlayMapViewController = CarPlayMapViewController(styles: styles) + window.rootViewController = carPlayMapViewController + self.carWindow = window + + let mapTemplate = self.mapTemplate(for: interfaceController) + mainMapTemplate = mapTemplate + interfaceController.setRootTemplate(mapTemplate, animated: false) + + eventsManager.sendCarPlayConnectEvent() + } + + public func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didDisconnectCarInterfaceController interfaceController: CPInterfaceController, from window: CPWindow) { + CarPlayManager.isConnected = false + self.interfaceController = nil + + window.rootViewController = nil + window.isHidden = true + window.removeFromSuperview() + + mainMapTemplate = nil + carWindow = nil + + eventsManager.sendCarPlayDisconnectEvent() + + if let shouldDisableIdleTimer = delegate?.carplayManagerShouldDisableIdleTimer(self) { + UIApplication.shared.isIdleTimerDisabled = !shouldDisableIdleTimer + } else { + UIApplication.shared.isIdleTimerDisabled = false + } + } +} + @available(iOS 12.0, *) internal protocol MapTemplateProviderDelegate: class { func mapTemplateProvider(_ provider: MapTemplateProvider, mapTemplate: CPMapTemplate, leadingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, for activity: CarPlayActivity) -> [CPBarButton]? @@ -717,14 +762,3 @@ internal class MapTemplateProvider: NSObject { return CPMapTemplate() } } -#else -/** - CarPlay support requires iOS 12.0 or above and the CarPlay framework. - */ -public class CarPlayManager: NSObject { - /** - A Boolean value indicating whether the phone is connected to CarPlay. - */ - public static var isConnected = false -} -#endif diff --git a/Sources/MapboxNavigation/CarPlayManagerDelegate.swift b/Sources/MapboxNavigation/CarPlayManagerDelegate.swift index 763b2af2afd..3b0ae8f3417 100644 --- a/Sources/MapboxNavigation/CarPlayManagerDelegate.swift +++ b/Sources/MapboxNavigation/CarPlayManagerDelegate.swift @@ -1,4 +1,3 @@ -#if canImport(CarPlay) import CarPlay import Turf import MapboxCoreNavigation @@ -153,6 +152,16 @@ public protocol CarPlayManagerDelegate: class, UnimplementedLogging { - returns: A Boolean value indicating whether to disable idle timer when carplay is connected and enable when disconnected. */ func carplayManagerShouldDisableIdleTimer(_ carPlayManager: CarPlayManager) -> Bool + + /** + Called when the CarPlayManager presents a new CarPlayNavigationViewController upon start of a navigation session. + + Implementing this method will allow developers to query or customize properties of the presented CarPlayNavigationViewController. For example, a developer may wish to perform custom map styling on the presented NavigationMapView. + + - parameter carPlayManager: The CarPlay manager instance. + - parameter navigationViewController: The CarPlayNavigationViewController that was presented on the CarPlay display. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) -> () } @available(iOS 12.0, *) @@ -247,5 +256,11 @@ public extension CarPlayManagerDelegate { logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) return false } + + /** + `UnimplementedLogging` prints a warning to standard output the first time this method is called. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) + } } -#endif diff --git a/Sources/MapboxNavigation/CarPlaySearchController+CPSearchTemplateDelegate.swift b/Sources/MapboxNavigation/CarPlaySearchController+CPSearchTemplateDelegate.swift index adb29c7eedc..8e30498099f 100644 --- a/Sources/MapboxNavigation/CarPlaySearchController+CPSearchTemplateDelegate.swift +++ b/Sources/MapboxNavigation/CarPlaySearchController+CPSearchTemplateDelegate.swift @@ -1,4 +1,4 @@ -#if canImport(CarPlay) && canImport(MapboxGeocoder) +#if canImport(MapboxGeocoder) import Foundation import CarPlay import MapboxGeocoder diff --git a/Sources/MapboxNavigation/CarPlaySearchController.swift b/Sources/MapboxNavigation/CarPlaySearchController.swift index c869e18f58d..08b02c1fcb7 100644 --- a/Sources/MapboxNavigation/CarPlaySearchController.swift +++ b/Sources/MapboxNavigation/CarPlaySearchController.swift @@ -1,4 +1,3 @@ -#if canImport(CarPlay) import CarPlay import MapboxDirections @@ -39,9 +38,3 @@ public class CarPlaySearchController: NSObject { */ public weak var delegate: CarPlaySearchControllerDelegate? } -#else -/** - CarPlay support requires iOS 12.0 or above and the CarPlay framework. - */ -public class CarPlaySearchController: NSObject {} -#endif diff --git a/Sources/MapboxNavigation/CongestionLevel.swift b/Sources/MapboxNavigation/CongestionLevel.swift index e9fa7c0315d..ed17409b842 100644 --- a/Sources/MapboxNavigation/CongestionLevel.swift +++ b/Sources/MapboxNavigation/CongestionLevel.swift @@ -1,6 +1,5 @@ import Foundation import MapboxDirections -#if canImport(CarPlay) import CarPlay extension CongestionLevel { @@ -23,4 +22,3 @@ extension CongestionLevel { } } } -#endif diff --git a/Sources/MapboxNavigation/DayStyle.swift b/Sources/MapboxNavigation/DayStyle.swift index a97d16dc0e9..27fac17789b 100644 --- a/Sources/MapboxNavigation/DayStyle.swift +++ b/Sources/MapboxNavigation/DayStyle.swift @@ -88,13 +88,11 @@ open class DayStyle: Style { Button.appearance().textColor = .defaultPrimaryText CancelButton.appearance().tintColor = .defaultPrimaryText - #if canImport(CarPlay) CarPlayCompassView.appearance().backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.6022227113) CarPlayCompassView.appearance().cornerRadius = 4 CarPlayCompassView.appearance().borderWidth = 1.0 / (UIScreen.mainCarPlay?.scale ?? 2.0) CarPlayCompassView.appearance().borderColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 0.6009573063) - #endif - + DismissButton.appearance().backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) DismissButton.appearance().textColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 1) DismissButton.appearance().textFont = UIFont.systemFont(ofSize: 20, weight: .medium).adjustedFont @@ -143,6 +141,10 @@ open class DayStyle: Style { LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).secondaryColor = .defaultLaneArrowSecondary LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).primaryColorHighlighted = .defaultLaneArrowPrimaryHighlighted LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).secondaryColorHighlighted = .defaultLaneArrowSecondaryHighlighted + LaneView.appearance().primaryColor = .defaultLaneArrowPrimaryCarPlay + LaneView.appearance().secondaryColor = .defaultLaneArrowSecondaryCarPlay + LaneView.appearance().primaryColorHighlighted = .defaultLaneArrowPrimaryHighlighted + LaneView.appearance().secondaryColorHighlighted = .defaultLaneArrowSecondaryHighlighted LanesView.appearance().backgroundColor = #colorLiteral(red: 0.968627451, green: 0.968627451, blue: 0.968627451, alpha: 1) LineView.appearance().lineColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.1) ManeuverView.appearance().backgroundColor = .clear diff --git a/Sources/MapboxNavigation/NavigationComponent.swift b/Sources/MapboxNavigation/NavigationComponent.swift index ac6356a17bf..ed99ec962a2 100644 --- a/Sources/MapboxNavigation/NavigationComponent.swift +++ b/Sources/MapboxNavigation/NavigationComponent.swift @@ -37,7 +37,12 @@ public protocol CarPlayConnectionObserver: class { */ public protocol NavigationStatusPresenter: class { /** - Shows the status view for a specified amount of time. + Shows a Status for a specified amount of time. */ - func showStatus(title: String, spinner: Bool, duration: TimeInterval, animated: Bool, interactive: Bool) + func show(_: StatusView.Status) + + /** + Hides a given Status without hiding the status view. + */ + func hide(_: StatusView.Status) } diff --git a/Sources/MapboxNavigation/NavigationViewController.swift b/Sources/MapboxNavigation/NavigationViewController.swift index d7b61f3e8f5..95772b11202 100644 --- a/Sources/MapboxNavigation/NavigationViewController.swift +++ b/Sources/MapboxNavigation/NavigationViewController.swift @@ -253,7 +253,12 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter } } - var styleManager: StyleManager! + /** + Controls the styling of NavigationViewController and its components. + + The style can be modified programmatically by using `StyleManager.applyStyle(type:)`. + */ + public private(set) var styleManager: StyleManager! var currentStatusBarStyle: UIStatusBarStyle = .default @@ -545,7 +550,10 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter guard AVAudioSession.sharedInstance().outputVolume <= NavigationViewMinimumVolumeForWarning else { return } let title = NSLocalizedString("INAUDIBLE_INSTRUCTIONS_CTA", bundle: .mapboxNavigation, value: "Adjust Volume to Hear Instructions", comment: "Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased") - showStatus(title: title, spinner: false, duration: 3, animated: true, interactive: false) + + // create low volume notification status and append to array of statuses + let lowVolumeStatus = StatusView.Status(identifier: "INAUDIBLE_INSTRUCTIONS_CTA", title: title, duration: 3, animated: true, priority: 3) + show(lowVolumeStatus) } // MARK: - Containerization methods @@ -618,9 +626,21 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter UNUserNotificationCenter.current().add(notificationRequest, withCompletionHandler: nil) } - public func showStatus(title: String, spinner: Bool, duration: TimeInterval, animated: Bool, interactive: Bool) { + /** + Shows a Status for a specified amount of time. + */ + public func show(_ status: StatusView.Status) { + navigationComponents.compactMap({ $0 as? NavigationStatusPresenter }).forEach { + $0.show(status) + } + } + + /** + Hides a given Status without hiding the status view. + */ + public func hide(_ status: StatusView.Status) { navigationComponents.compactMap({ $0 as? NavigationStatusPresenter }).forEach { - $0.showStatus(title: title, spinner: spinner, duration: duration, animated: animated, interactive: interactive) + $0.hide(status) } } } @@ -796,7 +816,13 @@ extension NavigationViewController: NavigationServiceDelegate { public func navigationServiceDidChangeAuthorization(_ service: NavigationService, didChangeAuthorizationFor locationManager: CLLocationManager) { if #available(iOS 14.0, *), locationManager.accuracyAuthorization == .reducedAccuracy { let title = NSLocalizedString("ENABLE_PRECISE_LOCATION", bundle: .mapboxNavigation, value: "Enable precise location to navigate", comment: "Label indicating precise location is off and needs to be turned on to navigate") - showStatus(title: title, spinner: false, duration: 20, animated: true, interactive: false) + show(StatusView.Status(identifier: "ENABLE_PRECISE_LOCATION", + title: title, + spinner: false, + duration: 20, + animated: true, + interactive: false, + priority: 1)) navigationMapView?.reducedAccuracyActivatedMode = true } else { // Fallback on earlier versions diff --git a/Sources/MapboxNavigation/NightStyle.swift b/Sources/MapboxNavigation/NightStyle.swift index 04d56a3b01d..0aa961f64c7 100644 --- a/Sources/MapboxNavigation/NightStyle.swift +++ b/Sources/MapboxNavigation/NightStyle.swift @@ -24,9 +24,7 @@ open class NightStyle: DayStyle { Button.appearance().textColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) CancelButton.appearance().tintColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) - #if canImport(CarPlay) CarPlayCompassView.appearance().backgroundColor = backgroundColor - #endif DismissButton.appearance().backgroundColor = backgroundColor DismissButton.appearance().textColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) @@ -47,7 +45,10 @@ open class NightStyle: DayStyle { FloatingButton.appearance().tintColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) GenericRouteShield.appearance().foregroundColor = .white InstructionsBannerView.appearance().backgroundColor = backgroundColor - LaneView.appearance().primaryColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) + LaneView.appearance().primaryColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + LaneView.appearance().secondaryColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.3) + LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).primaryColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).secondaryColor = #colorLiteral(red: 0.4198532104, green: 0.4398920536, blue: 0.4437610507, alpha: 1) LanesView.appearance().backgroundColor = backgroundColor ManeuverView.appearance().backgroundColor = .clear ManeuverView.appearance(whenContainedInInstancesOf: [InstructionsBannerView.self]).primaryColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) diff --git a/Sources/MapboxNavigation/RecentItem.swift b/Sources/MapboxNavigation/RecentItem.swift index e118f4edf24..92dd4b53005 100644 --- a/Sources/MapboxNavigation/RecentItem.swift +++ b/Sources/MapboxNavigation/RecentItem.swift @@ -1,4 +1,4 @@ -#if canImport(CarPlay) && canImport(MapboxGeocoder) +#if canImport(MapboxGeocoder) import Foundation import MapboxGeocoder import CarPlay diff --git a/Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings index 7d51c9b08ca..291dcfde4a0 100644 --- a/Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings @@ -166,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Other"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Alternative route not expected"; diff --git a/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.strings index 9d154976f31..5dba325bbb8 100644 --- a/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.strings @@ -1,26 +1,231 @@ +/* Delimiter between lines in an address when displayed inline */ +"ADDRESS_LINE_SEPARATOR" = ","; + +/* Title on arrival action sheet */ +"CARPLAY_ARRIVED" = "Heu arribat"; + +/* Message on arrival action sheet */ +"CARPLAY_ARRIVED_MESSAGE" = "Que us agradaria fer? "; + +/* Name of the waypoint associated with the current location */ +"CARPLAY_CURRENT_LOCATION" = "Posició actual"; + +/* Title for dismiss button */ +"CARPLAY_DISMISS" = "Descartar"; + +/* Title for end navigation button */ +"CARPLAY_END" = "Fi"; + +/* Title on the exit button in the arrival form */ +"CARPLAY_EXIT_NAVIGATION" = "Sortir de la navegació"; + +/* Title for feedback template in CarPlay */ +"CARPLAY_FEEDBACK" = "Comentaris"; + +/* Title for start button in CPTripPreviewTextConfiguration */ +"CARPLAY_GO" = "Endavant"; + +/* Title for alternative routes in CPTripPreviewTextConfiguration */ +"CARPLAY_MORE_ROUTES" = "Més rutes"; + +/* Title for mute button */ +"CARPLAY_MUTE" = "Silenciar"; + +/* Title for overview button in CPTripPreviewTextConfiguration */ +"CARPLAY_OVERVIEW" = "Visió General"; + +/* Title for rating template in CarPlay */ +"CARPLAY_RATE_RIDE" = "Valora el viatge"; + +/* Title on rate button in CarPlay */ +"CARPLAY_RATE_TRIP" = "Valora el viatge"; + +/* Message when search returned zero results in CarPlay */ +"CARPLAY_SEARCH_NO_RESULTS" = "Sense resultats"; + +/* Alert title that shows when feedback has been submitted */ +"CARPLAY_SUBMITTED_FEEDBACK" = "Enviat"; + +/* Title for unmute button */ +"CARPLAY_UNMUTE" = "Deixar de silenciar"; + +/* Specific route feedback that audio guidance provided was confusing. */ +"CONFUSING_AUDIO_FEEDBACK" = "Àudio confús"; + +/* Specific route feedback that audio guidance was provided too early before a maneuever. */ +"CONFUSING_AUDIO_GUIDANCE_TOO_EARLY_FEEDBACK" = "Orientació massa aviat"; + +/* Specific route feedback that audio guidance was provided too late for a maneuever. */ +"CONFUSING_AUDIO_GUIDANCE_TOO_LATE_FEEDBACK" = "Orientació massa tard"; + +/* Specific route feedback that an audio guidance problem was encountered but not listed as a choice. */ +"CONFUSING_AUDIO_OTHER_FEEDBACK" = "Altres"; + +/* Specific route feedback that audio guidance used incorrect pronunciation. */ +"CONFUSING_AUDIO_PRONUNCIATION_INCORRECT_FEEDBACK" = "Pronunciació incorrecta"; + +/* Specific route feedback that audio guidance repeated a road name. */ +"CONFUSING_AUDIO_ROADNAME_REPEATED_FEEDBACK" = "Nom de vial repetit"; + +/* Dismiss button title on the steps view */ +"DISMISS_STEPS_TITLE" = "Tancar"; + +/* Label indicating precise location is off and needs to be turned on to navigate */ +"ENABLE_PRECISE_LOCATION" = "Activeu la localització precisa per navegar"; + +/* Title used for arrival */ +"END_OF_ROUTE_ARRIVED" = "Heu arribatHeu arribat"; + +/* Comment Placeholder Text */ +"END_OF_ROUTE_TITLE" = "Com podem millorar?"; + /* Indicates a faster route was found */ "FASTER_ROUTE_FOUND" = "S’ha trobat una ruta més ràpida"; /* Message confirming that the user has successfully sent feedback */ "FEEDBACK_THANK_YOU" = "Gràcies pel vostre informe!"; +/* Title of view controller for sending feedback */ +"FEEDBACK_TITLE" = "Informar d'un problema"; + +/* Specific route feedback that a illegal route problem was encountered but not listed as a choice. */ +"ILLEGAL_ROUTE_OTHER_FEEDBACK" = "Altres"; + +/* Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased */ +"INAUDIBLE_INSTRUCTIONS_CTA" = "Ajusteu el volum per sentir les instruccions"; + +/* Specific route feedback that a speed limit is incorrect. */ +"INCORRECT_SPEED_LIMIT" = "Límit de velocitat incorrecte"; + +/* Specific route feedback that an exit was incorrect. */ +"INCORRECT_VISUAL_EXIT_INFO_INCORRECT_FEEDBACK" = "Informació de sortida incorrecta"; + +/* General category of route feedback where visual instruction was incorrect. */ +"INCORRECT_VISUAL_FEEDBACK" = "Sembla incorrecte"; + +/* Specific route feedback that an instruction was missing. */ +"INCORRECT_VISUAL_INSTRUCTION_MISSING_FEEDBACK" = "Falten instruccions"; + +/* Specific route feedback for an unnecessary instruction. */ +"INCORRECT_VISUAL_INSTRUCTION_UNNECESSARY_FEEDBACK" = "Instruccions innecessàries"; + +/* Specific route feedback that the wrong lane was specified. */ +"INCORRECT_VISUAL_LANE_GUIDANCE_INCORRECT_FEEDBACK" = "Guia de carril incorrecta"; + +/* Specific route feedback that a maneuver specified was incorrect. */ +"INCORRECT_VISUAL_MANEUVER_INCORRECT_FEEDBACK" = "Maniobra incorrecta"; + +/* Specific route feedback that a visual instruction problem was encountered but not listed as a choice. */ +"INCORRECT_VISUAL_OTHER_FEEDBACK" = "Altres"; + +/* Specific route feedback that a road is known by another name. */ +"INCORRECT_VISUAL_ROAD_NAME_DIFFERENT_FEEDBACK" = "Carretera coneguda amb un nom diferent"; + +/* Specific route feedback for incorrect street name. */ +"INCORRECT_VISUAL_STREET_NAME_INCORRECT_FEEDBACK" = "Nom de carrer incorrecte"; + +/* Specific route feedback for incorrect turn arrow being shown. */ +"INCORRECT_VISUAL_TURN_ICON_INCORRECT_FEEDBACK" = "Icona de gir incorrecta"; + /* Format for displaying the first two major ways */ "LEG_MAJOR_WAYS_FORMAT" = "%1$@ i %2$@"; /* Format string for a short distance or time less than a minimum threshold; 1 = duration remaining */ "LESS_THAN" = "<%@"; +/* Title for button that cancels user's submission of feedback on navigation session issues. */ +"NAVIGATION_REPORT_CANCEL" = "Cancel·lar"; + +/* Title for button that submits user's feedback on multiple navigation session issues. 1 is the number of items */ +"NAVIGATION_REPORT_ISSUES" = "Enviar %ld article(s)"; + +/* Accessibility value of label indicating the absence of a rating */ +"NO_RATING" = "Sense valoració."; + +/* General category of route feedback where user position is incorrect. */ +"POSITIONING" = "Posicionament"; + +/* Specific route feedback that user positioning is incorrect. */ +"POSITIONING_USER" = "La vostra posició"; + +/* Rating Reset To Zero Accessability Hint */ +"RATING_ACCESSIBILITY_RESET" = "Toca per restablir la valoració a zero."; + +/* Format for accessibility label of button for setting a rating; 1 = number of stars */ +"RATING_ACCESSIBILITY_SET" = "Estableix una puntuació de %ld-estels"; + +/* Format for accessibility value of label indicating the existing rating; 1 = number of stars */ +"RATING_STARS_FORMAT" = "%ld estel(s) afegits."; + /* Indicates that rerouting is in progress */ "REROUTING" = "Recalculant la ruta…"; /* Button title for resume tracking */ "RESUME" = "Continuar"; +/* Specific route feedback that a road closure type was encountered but not listed as a choice. */ +"ROAD_CLOSURE_OTHER_FEEDBACK" = "Altres"; + +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "Sense peatges"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Peatges"; + +/* Specific route feedback that user was offered an alternative that was unexpected. */ +"ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "No s’espera una ruta alternativa"; + +/* General category of route feedback where route quality was poor. */ +"ROUTE_QUALITY_FEEDBACK" = "Qualitat de la ruta"; + +/* Specific route feedback that route suggested an illegal roadway. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_CARS_NOT_ALLOWED_FEEDBACK" = "No es permeten cotxes al carrer"; + +/* General route feedback that route contained illegal instructions. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_FEEDBACK" = "Ruta il·legal"; + +/* Specific route feedback that route travelled wrong way on a one-way road. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_ONE_WAY_FEEDBACK" = "Dirigit cap a un vial de sentit únic"; + +/* Specific route feedback that route suggested an illegal turn. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_TURN_NOT_ALLOWED_FEEDBACK" = "No es podia girar"; + +/* Specific route feedback that route suggested an illegal unprotected turn. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_UNPROTECTED_TURN_FEEDBACK" = "Gir desprotegit a la intersecció"; + +/* Specific route feedback that route contained non-existant roads. */ +"ROUTE_QUALITY_INCLUDED_MISSING_ROADS_FEEDBACK" = "La ruta inclou carreteres que falten"; + +/* Specific route feedback that route was not drivable. */ +"ROUTE_QUALITY_NON_DRIVABLE_FEEDBACK" = "No es pot conduïr per aquesta ruta"; + +/* Specific route feedback that route was not ideal according to user. */ +"ROUTE_QUALITY_NOT_PREFERRED_FEEDBACK" = "Aquesta ruta no és la preferida"; + +/* Specific route feedback that a route quality problem was encountered but not listed as a choice. */ +"ROUTE_QUALITY_OTHER_FEEDBACK" = "Altres"; + +/* General route feedback that route contained closed road. */ +"ROUTE_QUALITY_ROAD_CLOSURE_FEEDBACK" = "Carretera tancada"; + +/* Specifc route feedback that route contained road that is permanently closed. */ +"ROUTE_QUALITY_ROAD_CLOSURE_PERMANENT_FEEDBACK" = "Carrer bloquejat permanentment"; + +/* Specifc route feedback that route contained road not shown on map. */ +"ROUTE_QUALITY_ROAD_MISSING_FROM_MAP_FEEDBACK" = "Falta la carretera al mapa"; + +/* Specific route feedback that route contained impassible, narrow roads. */ +"ROUTE_QUALITY_ROADS_TOO_NARROW_FEEDBACK" = "La ruta tenia passos massa estrets"; + +/* Label above the speed limit in an MUTCD-style speed limit sign. Keep as short as possible. */ +"SPEED_LIMIT_LEGEND" = "Màx"; + /* The text of a banner that appears during turn-by-turn navigation when route simulation is enabled. */ -"USER_IN_SIMULATION_MODE" = "Simular la navegació"; +"USER_IN_SIMULATION_MODE" = "Simular navegació a %@"; /* Format for displaying destination and intermediate waypoints; 1 = source ; 2 = destinations */ "WAYPOINT_DESTINATION_VIA_WAYPOINTS_FORMAT" = "%1$@, via %2$@"; /* Format for displaying start and endpoint for leg; 1 = source ; 2 = destination */ "WAYPOINT_SOURCE_DESTINATION_FORMAT" = "%1$@ i %2$@"; + diff --git a/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.stringsdict b/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.stringsdict new file mode 100644 index 00000000000..779bf8dae3a --- /dev/null +++ b/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.stringsdict @@ -0,0 +1,54 @@ + + + + + RATING_ACCESSIBILITY_SET + + NSStringLocalizedFormatKey + %#@stars@ + stars + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Estableix una puntuació de %ld-estels + other + Estableix una puntuació de %ld-estels + + + RATING_STARS_FORMAT + + NSStringLocalizedFormatKey + %#@stars@ + stars + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld estels assignats. + other + %ld estels assignats. + + + NAVIGATION_REPORT_ISSUES + + NSStringLocalizedFormatKey + %#@items@ + items + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Enviar 1 element + other + Enviar %ld elements + + + + diff --git a/Sources/MapboxNavigation/Resources/ca.lproj/Navigation.strings b/Sources/MapboxNavigation/Resources/ca.lproj/Navigation.strings index 97ffa32f745..a48c493c72f 100644 --- a/Sources/MapboxNavigation/Resources/ca.lproj/Navigation.strings +++ b/Sources/MapboxNavigation/Resources/ca.lproj/Navigation.strings @@ -1,6 +1,6 @@ -/* Class = "UILabel"; text = "Rerouting…"; ObjectID = "UpZ-gr-OKM"; */ -"UpZ-gr-OKM.text" = "Recalculant la ruta…"; +/* Class = "UIButton"; normalTitle = "End Navigation"; ObjectID = "5f2-dT-la4"; */ +"5f2-dT-la4.normalTitle" = "Finalitza la navegació"; -/* Class = "UILabel"; text = "Recording Audio"; ObjectID = "xTH-eK-F5H"; */ -"xTH-eK-F5H.text" = "Enregistrant àudio"; +/* Class = "UILabel"; text = "Rate your trip"; ObjectID = "W5U-cV-cDO"; */ +"W5U-cV-cDO.text" = "Valora el viatge"; diff --git a/Sources/MapboxNavigation/Resources/de.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/de.lproj/Localizable.strings index 368c2c8c93e..c5e4bf99d5e 100644 --- a/Sources/MapboxNavigation/Resources/de.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/de.lproj/Localizable.strings @@ -70,6 +70,9 @@ /* Dismiss button title on the steps view */ "DISMISS_STEPS_TITLE" = "Schließen"; +/* Label indicating precise location is off and needs to be turned on to navigate */ +"ENABLE_PRECISE_LOCATION" = "Enable precise location to navigate"; + /* Title used for arrival */ "END_OF_ROUTE_ARRIVED" = "Du bist angekommen"; @@ -91,6 +94,9 @@ /* Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased */ "INAUDIBLE_INSTRUCTIONS_CTA" = "Lautstärke anpassen, um Anweisungen zu hören"; +/* Specific route feedback that a speed limit is incorrect. */ +"INCORRECT_SPEED_LIMIT" = "Speed limit incorrect"; + /* Specific route feedback that an exit was incorrect. */ "INCORRECT_VISUAL_EXIT_INFO_INCORRECT_FEEDBACK" = "Ausfahrtsinformation falsch"; @@ -160,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Anderes"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Alternative Route war unerwartet"; diff --git a/Sources/MapboxNavigation/Resources/es.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/es.lproj/Localizable.strings index 6ad13f2edc2..91dfcf279bd 100644 --- a/Sources/MapboxNavigation/Resources/es.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/es.lproj/Localizable.strings @@ -70,6 +70,9 @@ /* Dismiss button title on the steps view */ "DISMISS_STEPS_TITLE" = "Cerrar"; +/* Label indicating precise location is off and needs to be turned on to navigate */ +"ENABLE_PRECISE_LOCATION" = "Activar la ubicación precisa para navigar"; + /* Title used for arrival */ "END_OF_ROUTE_ARRIVED" = "Ha llegado"; @@ -91,6 +94,9 @@ /* Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased */ "INAUDIBLE_INSTRUCTIONS_CTA" = "Ajustar el volumen para escuchar las instrucciones"; +/* Specific route feedback that a speed limit is incorrect. */ +"INCORRECT_SPEED_LIMIT" = "Límite de velocidad incorrecto"; + /* Specific route feedback that an exit was incorrect. */ "INCORRECT_VISUAL_EXIT_INFO_INCORRECT_FEEDBACK" = "Detalles de la salida incorrectos"; @@ -160,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Otro"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Ruta alternativa inesperada"; diff --git a/Sources/MapboxNavigation/Resources/uk.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/uk.lproj/Localizable.strings index e370af3efae..3f9c7d78287 100644 --- a/Sources/MapboxNavigation/Resources/uk.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/uk.lproj/Localizable.strings @@ -166,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Інше"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Неочікуваний альтернативний маршрут"; diff --git a/Sources/MapboxNavigation/Resources/vi.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/vi.lproj/Localizable.strings index e0940807a4c..01b2a210fad 100644 --- a/Sources/MapboxNavigation/Resources/vi.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/vi.lproj/Localizable.strings @@ -70,6 +70,9 @@ /* Dismiss button title on the steps view */ "DISMISS_STEPS_TITLE" = "Đóng"; +/* Label indicating precise location is off and needs to be turned on to navigate */ +"ENABLE_PRECISE_LOCATION" = "Bật vị trí chính xác để điều hướng"; + /* Title used for arrival */ "END_OF_ROUTE_ARRIVED" = "Đã đến nơi rồi"; @@ -91,6 +94,9 @@ /* Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased */ "INAUDIBLE_INSTRUCTIONS_CTA" = "Điều chỉnh Âm lượng để Nghe Hướng dẫn"; +/* Specific route feedback that a speed limit is incorrect. */ +"INCORRECT_SPEED_LIMIT" = "Tốc độ tối đa không chính xác"; + /* Specific route feedback that an exit was incorrect. */ "INCORRECT_VISUAL_EXIT_INFO_INCORRECT_FEEDBACK" = "Chi tiết đường nhánh sai"; @@ -160,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Khác"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Tuyến đường thay thế bất ngờ"; diff --git a/Sources/MapboxNavigation/StatusView.swift b/Sources/MapboxNavigation/StatusView.swift index 1e3b4121d5b..01ec3006900 100644 --- a/Sources/MapboxNavigation/StatusView.swift +++ b/Sources/MapboxNavigation/StatusView.swift @@ -18,6 +18,58 @@ public class StatusView: UIControl { } } + var statuses: [Status] = [] + + /** + `Status` is a struct which stores information to be displayed by the `StatusView` + */ + public struct Status { + /** + A string that uniquely identifies the `Status` + */ + public var identifier: String + /** + The text that will appear on the `Status` + */ + public let title: String + /** + A boolean that indicates whether a `spinner` should be shown during animations + set to `false` by default + */ + public var spinner: Bool = false + /** + A TimeInterval that designates the length of time the `Status` will be displayed in seconds + To display the `Status` indefinitely, set `duration` to `.infinity` + */ + public let duration: TimeInterval + /** + A boolean that indicates whether showing and hiding of the `Status` should be animated + set to `true` by default + */ + public var animated: Bool = true + /** + A boolean that indicates whether the `Status` should respond to touch events + set to `false` by default + */ + public var interactive: Bool = false + /** + A typealias which is used to rank a `Status` by importance + A lower `priority` value corresponds to a higher priority + */ + public var priority: Priority + } + + /** + `Priority` is used to display `Status`es by importance + Lower values correspond to higher priority. + */ + public typealias Priority = Int +// — Highest Priority — +// rerouting (value = 0) +// enable precise location (value = 1) +// simulation banner (value = 2) +// — Lowest Priority — + public override init(frame: CGRect) { super.init(frame: frame) commonInit() @@ -89,39 +141,69 @@ public class StatusView: UIControl { } } - public func showStatus(title: String, spinner spin: Bool = false, duration: TimeInterval, animated: Bool = true, interactive: Bool = false) { - show(title, showSpinner: spin, interactive: interactive) - guard duration < .infinity else { return } - hide(delay: duration, animated: animated) + /** + Adds a new status to statuses array. + */ + func show(_ status: Status) { + if let row = statuses.firstIndex(where: {$0.identifier.contains(status.identifier)}) { + statuses[row] = status + } else { + statuses.append(status) + } + manageStatuses() + } + + /** + Manages showing and hiding Statuses and the status view itself. + */ + func manageStatuses(status: Status? = nil) { + if statuses.isEmpty { + hide(delay: status?.duration ?? 0, animated: status?.animated ?? true) + } else { + // if we hide a Status and there are Statuses left in the statuses array, show the Status with highest priority + guard let highestPriorityStatus = statuses.min(by: {$0.priority < $1.priority}) else { return } + show(status: highestPriorityStatus) + hide(with: highestPriorityStatus, delay: highestPriorityStatus.duration) + } + } + + /** + Hides a given Status without hiding the status view. + */ + func hide(_ status: Status?) { + guard let row = statuses.firstIndex(where: {$0.identifier == status?.identifier}) else { return } + let removedStatus = statuses.remove(at: row) + manageStatuses(status: removedStatus) } func showSimulationStatus(speed: Int) { let format = NSLocalizedString("USER_IN_SIMULATION_MODE", bundle: .mapboxNavigation, value: "Simulating Navigation at %@×", comment: "The text of a banner that appears during turn-by-turn navigation when route simulation is enabled.") let title = String.localizedStringWithFormat(format, NumberFormatter.localizedString(from: speed as NSNumber, number: .decimal)) - showStatus(title: title, duration: .infinity, interactive: true) + let simulationStatus = Status(identifier: "USER_IN_SIMULATION_MODE", title: title, duration: .infinity, interactive: true, priority: 2) + show(simulationStatus) } /** Shows the status view with an optional spinner. */ - public func show(_ title: String, showSpinner: Bool, interactive: Bool = false) { - isEnabled = interactive - textLabel.text = title + public func show(status: Status) { + isEnabled = status.interactive + textLabel.text = status.title activityIndicatorView.hidesWhenStopped = true - if (!showSpinner) { activityIndicatorView.stopAnimating() } + if (!status.spinner) { activityIndicatorView.stopAnimating() } guard !isCurrentlyVisible, isHidden else { return } let show = { self.isHidden = false self.textLabel.alpha = 1 - if (showSpinner) { self.activityIndicatorView.isHidden = false } + if (status.spinner) { self.activityIndicatorView.isHidden = false } self.superview?.layoutIfNeeded() } UIView.defaultAnimation(0.3, animations:show, completion:{ _ in self.isCurrentlyVisible = true - guard showSpinner else { return } + guard status.spinner else { return } self.activityIndicatorView.startAnimating() }) } @@ -129,22 +211,30 @@ public class StatusView: UIControl { /** Hides the status view. */ - public func hide(delay: TimeInterval = 0, animated: Bool = true) { + public func hide(with status: Status? = nil, delay: TimeInterval = 0, animated: Bool = true) { let hide = { - self.isHidden = true - self.textLabel.alpha = 0 - self.activityIndicatorView.isHidden = true + if status == nil { + self.isHidden = true + self.textLabel.alpha = 0 + self.activityIndicatorView.isHidden = true + } else { + self.hide(status) + } } let animate = { let fireTime = DispatchTime.now() + delay DispatchQueue.main.asyncAfter(deadline: fireTime, execute: { - guard !self.isHidden, self.isCurrentlyVisible else { return } - - self.activityIndicatorView.stopAnimating() - UIView.defaultAnimation(0.3, delay: 0, animations: hide, completion: { _ in - self.isCurrentlyVisible = false - }) + if status == nil { + guard !self.isHidden, self.isCurrentlyVisible else { return } + + self.activityIndicatorView.stopAnimating() + UIView.defaultAnimation(0.3, delay: 0, animations: hide, completion: { _ in + self.isCurrentlyVisible = false + }) + } else { + self.hide(status) + } }) } diff --git a/Sources/MapboxNavigation/StyleManager.swift b/Sources/MapboxNavigation/StyleManager.swift index fb27b2861c2..35fb5daaf49 100644 --- a/Sources/MapboxNavigation/StyleManager.swift +++ b/Sources/MapboxNavigation/StyleManager.swift @@ -12,6 +12,14 @@ public protocol StyleManagerDelegate: class, UnimplementedLogging { */ func location(for styleManager: StyleManager) -> CLLocation? + /** + Asks the delegate for the view to be used when refreshing appearance. + + The default implementation of this method will attempt to cast the delegate to type + `UIViewController` and use its `view` property. + */ + func styleManager(_ styleManager: StyleManager, viewForApplying currentStyle: Style?) -> UIView? + /** Informs the delegate that a style was applied. @@ -47,6 +55,16 @@ public extension StyleManagerDelegate { func styleManagerDidRefreshAppearance(_ styleManager: StyleManager) { logUnimplemented(protocolType: StyleManagerDelegate.self, level: .debug) } + + func styleManager(_ styleManager: StyleManager, viewForApplying currentStyle: Style?) -> UIView? { + // Short-circuit refresh logic if the view hasn't yet loaded since we don't want the `self.view` + // call to trigger `loadView`. + if let vc = self as? UIViewController, vc.isViewLoaded { + return vc.view + } + + return nil + } } /** @@ -88,8 +106,16 @@ open class StyleManager { internal var date: Date? private var timeOfDayTimer: Timer? - var currentStyleType: StyleType? - private(set) var currentStyle: Style? { + /** + The currently applied style. Use `StyleManager.applyStyle(type:)` to update this value. + */ + public private(set) var currentStyleType: StyleType? + + /** + The current style associated with `currentStyleType`. Calling `StyleManager.applyStyle(type:)` will + result in this value being updated. + */ + public private(set) var currentStyle: Style? { didSet { guard let style = currentStyle else { return } postDidApplyStyleNotification(style: style) @@ -132,12 +158,10 @@ open class StyleManager { print("Unable to get sunrise or sunset. Automatic style switching has been disabled.") return } - - timeOfDayTimer = Timer(timeInterval: interval + 1, - repeats: false, - block: { [weak self] _ in + + timeOfDayTimer = Timer.scheduledTimer(withTimeInterval: interval + 1, repeats: false) { [weak self] _ in self?.timeOfDayChanged() - }) + } } @objc func preferredContentSizeChanged(_ notification: Notification) { @@ -149,7 +173,10 @@ open class StyleManager { resetTimeOfDayTimer() } - func applyStyle(type styleType: StyleType) { + /** + Applies the `Style` with type matching `type`and notifies `StyleManager.delegate` upon completion. + */ + public func applyStyle(type styleType: StyleType) { guard currentStyleType != styleType else { return } NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(timeOfDayChanged), object: nil) @@ -160,6 +187,7 @@ open class StyleManager { currentStyleType = styleType currentStyle = style delegate?.styleManager(self, didApply: style) + break } } @@ -227,13 +255,15 @@ open class StyleManager { forceRefreshAppearance() } - // workaround to refresh appearance by removing all views and then adding them again + // workaround to refresh appearance by removing the view and then adding it again func forceRefreshAppearance() { - for window in UIApplication.shared.windows { - for view in window.subviews { - view.removeFromSuperview() - window.addSubview(view) - } + if + let view = delegate?.styleManager(self, viewForApplying: currentStyle), + let superview = view.superview, + let index = superview.subviews.firstIndex(of: view) + { + view.removeFromSuperview() + superview.insertSubview(view, at: index) } delegate?.styleManagerDidRefreshAppearance(self) diff --git a/Sources/MapboxNavigation/TopBannerViewController.swift b/Sources/MapboxNavigation/TopBannerViewController.swift index d10eb428f54..36f013515a8 100644 --- a/Sources/MapboxNavigation/TopBannerViewController.swift +++ b/Sources/MapboxNavigation/TopBannerViewController.swift @@ -386,7 +386,8 @@ extension TopBannerViewController: NavigationComponent { public func navigationService(_ service: NavigationService, willRerouteFrom location: CLLocation) { let title = NSLocalizedString("REROUTING", bundle: .mapboxNavigation, value: "Rerouting…", comment: "Indicates that rerouting is in progress") lanesView.hide() - statusView.show(title, showSpinner: true) + let reroutingStatus = StatusView.Status(identifier: "REROUTING", title: title, duration: 20, priority: 0) + show(reroutingStatus) } public func navigationService(_ service: NavigationService, didRerouteAlong route: Route, at location: CLLocation?, proactive: Bool) { @@ -401,7 +402,10 @@ extension TopBannerViewController: NavigationComponent { if (proactive) { let title = NSLocalizedString("FASTER_ROUTE_FOUND", bundle: .mapboxNavigation, value: "Faster Route Found", comment: "Indicates a faster route was found") - statusView.showStatus(title: title, spinner: true, duration: 3) + + // create faster route status and append to array of statuses + let fasterRouteStatus = StatusView.Status(identifier: "FASTER_ROUTE_FOUND", title: title, duration: 3, priority: 0) + statusView.show(fasterRouteStatus) } } @@ -463,8 +467,12 @@ extension TopBannerViewController: CarPlayConnectionObserver { } extension TopBannerViewController: NavigationStatusPresenter { - public func showStatus(title: String, spinner spin: Bool, duration time: TimeInterval, animated: Bool, interactive: Bool) { - statusView.showStatus(title: title, spinner: spin, duration: time, animated: animated, interactive: interactive) + public func show(_ status: StatusView.Status) { + statusView.show(status) + } + + public func hide(_ status: StatusView.Status) { + statusView.hide(status) } } diff --git a/Sources/MapboxNavigation/UIScreen.swift b/Sources/MapboxNavigation/UIScreen.swift index 187261c8c92..d0a445d4a49 100644 --- a/Sources/MapboxNavigation/UIScreen.swift +++ b/Sources/MapboxNavigation/UIScreen.swift @@ -1,5 +1,4 @@ import Foundation -#if canImport(CarPlay) import CarPlay extension UIScreen { @@ -7,4 +6,3 @@ extension UIScreen { return UIScreen.screens.filter { $0.traitCollection.containsTraits(in: UITraitCollection(userInterfaceIdiom: .carPlay)) }.first } } -#endif diff --git a/Sources/MapboxNavigation/VisualInstruction.swift b/Sources/MapboxNavigation/VisualInstruction.swift index 1bc9544639b..88172a19bb3 100644 --- a/Sources/MapboxNavigation/VisualInstruction.swift +++ b/Sources/MapboxNavigation/VisualInstruction.swift @@ -1,7 +1,5 @@ import MapboxDirections -#if canImport(CarPlay) import CarPlay -#endif extension VisualInstruction { var laneComponents: [Component] { @@ -73,7 +71,6 @@ extension VisualInstruction { return newImage } - #if canImport(CarPlay) /// Returns a `CPImageSet` representing the maneuver. @available(iOS 12.0, *) public func maneuverImageSet(side: DrivingSide) -> CPImageSet? { @@ -172,5 +169,4 @@ extension VisualInstruction { } return nil } - #endif } diff --git a/Tests/MapboxNavigationTests/CPMapTemplateTests.swift b/Tests/MapboxNavigationTests/CPMapTemplateTests.swift index 86c026a17e4..4c28edff043 100644 --- a/Tests/MapboxNavigationTests/CPMapTemplateTests.swift +++ b/Tests/MapboxNavigationTests/CPMapTemplateTests.swift @@ -1,6 +1,5 @@ import XCTest @testable import MapboxNavigation -#if canImport(CarPlay) import CarPlay @available(iOS 12.0, *) @@ -20,4 +19,3 @@ class CPMapTemplateTests: XCTestCase { XCTAssertNil(CLLocationDirection(panDirection: [])) } } -#endif diff --git a/Tests/MapboxNavigationTests/CarPlayManagerTests.swift b/Tests/MapboxNavigationTests/CarPlayManagerTests.swift index c0756e19789..90601b116d8 100644 --- a/Tests/MapboxNavigationTests/CarPlayManagerTests.swift +++ b/Tests/MapboxNavigationTests/CarPlayManagerTests.swift @@ -5,7 +5,6 @@ import MapboxMobileEvents @testable import TestHelper @testable import MapboxNavigation -#if canImport(CarPlay) import CarPlay // For some reason XCTest bundles ignore @available annotations and these tests are run on iOS < 12 :( @@ -445,6 +444,10 @@ class CarPlayManagerSpec: QuickSpec { let directionsFake = Directions(credentials: Fixture.credentials) return MapboxNavigationService(route: route, routeIndex: routeIndex, routeOptions: routeOptions, directions: directionsFake, simulating: desiredSimulationMode) } + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + //no-op + } } } @@ -483,6 +486,10 @@ class CarPlayManagerFailureDelegateSpy: CarPlayManagerDelegate { func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) { fatalError("This is an empty stub.") } + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + fatalError("This is an empty stub.") + } } //MARK: Test Objects / Classes. @@ -530,6 +537,10 @@ class TestCarPlayManagerDelegate: CarPlayManagerDelegate { navigationEnded = true currentService = nil } + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + XCTAssertTrue(navigationInitiated) + } } @available(iOS 12.0, *) @@ -657,4 +668,3 @@ class FakeCPInterfaceController: CPInterfaceController { } } } -#endif diff --git a/Tests/MapboxNavigationTests/StatusViewTests.swift b/Tests/MapboxNavigationTests/StatusViewTests.swift new file mode 100644 index 00000000000..6c381ed4c92 --- /dev/null +++ b/Tests/MapboxNavigationTests/StatusViewTests.swift @@ -0,0 +1,81 @@ +import XCTest +import UIKit +@testable import MapboxNavigation +@testable import MapboxCoreNavigation + +class StatusViewTests: XCTestCase { + lazy var statusView: StatusView = { + let view: StatusView = .forAutoLayout() + view.isHidden = true + return view + }() + + func testWithDelayShorterThanDuration() { + show(firstStatus()) + XCTAssertEqual(self.statusView.statuses.count, 1) + } + + func testWithDelayLongerThanDuration() { + let seconds = 5.0 + XCTAssertTrue(statusView.isHidden) + show(firstStatus()) + XCTAssertFalse(statusView.isHidden) + let path = #keyPath(UIView.isHidden) + let expectation = XCTKVOExpectation(keyPath: path, object: statusView, expectedValue: true) + self.wait(for: [expectation], timeout: seconds) + XCTAssertTrue(statusView.isHidden) + XCTAssertEqual(self.statusView.statuses.count, 0) + } + + func testFirstAndSecond() { + show(firstStatus()) + XCTAssertFalse(statusView.isHidden) + show(secondStatus()) + XCTAssertEqual(statusView.statuses.count, 2) + let path = #keyPath(UIView.isHidden) + let expectation = XCTKVOExpectation(keyPath: path, object: statusView, expectedValue: true) + self.wait(for: [expectation], timeout: secondStatus().duration + 10.0) + XCTAssertTrue(statusView.isHidden) + XCTAssertEqual(self.statusView.statuses.count, 0) + } + + func testWithInfinite() { + show(firstStatus()) + show(thirdStatus()) + XCTAssertEqual(self.statusView.statuses.count, 2) + let path = #keyPath(UIView.isHidden) + let expectation = XCTKVOExpectation(keyPath: path, object: statusView, expectedValue: true) + XCTWaiter.wait(for: [expectation], timeout: 15.0) + XCTAssertFalse(statusView.isHidden) + XCTAssertEqual(self.statusView.statuses.count, 1) + } +} + +extension StatusViewTests { + + // define statuses + func firstStatus() -> StatusView.Status { + return StatusView.Status(identifier: "FIRST_TEST_STATUS", title: "first test status", duration: 1, priority: 0) + } + + func secondStatus() -> StatusView.Status { + return StatusView.Status(identifier: "SECOND_TEST_STATUS", title: "second test status", duration: 5, priority: 1) + } + + func thirdStatus() -> StatusView.Status { + return StatusView.Status(identifier: "THIRD_TEST_STATUS", title: "third test status", duration: .infinity, priority: 2) + } + + func show(_ status: StatusView.Status) { + statusView.show(status) + } + + func hide(_ status: StatusView.Status) { + statusView.hide(status) + } + + func clearStatuses() { + statusView.statuses.removeAll() + } +} + diff --git a/custom-navigation.md b/custom-navigation.md index 4fa77ee79d4..46f603d6ef0 100644 --- a/custom-navigation.md +++ b/custom-navigation.md @@ -57,7 +57,7 @@ To install Mapbox Core Navigation using [CocoaPods](https://cocoapods.org/): 1. Create a [Podfile](https://guides.cocoapods.org/syntax/podfile.html) with the following specification: ```ruby # Latest stable release - pod 'MapboxCoreNavigation', '~> 1.2' + pod 'MapboxCoreNavigation', '~> 1.4' # Latest prerelease pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.8' ``` @@ -84,11 +84,49 @@ To install Mapbox Navigation using [Carthage](https://github.com/Carthage/Cartha 1. Create a [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#github-repositories) with the following dependency: ```cartfile # Latest stable release - github "mapbox/mapbox-navigation-ios" ~> 1.2 + github "mapbox/mapbox-navigation-ios" ~> 1.4 # Latest prerelease github "mapbox/mapbox-navigation-ios" "v2.0.0-beta.8" ``` 1. Run `./Carthage/Checkouts/mapbox-navigation-ios/scripts/wcarthage.sh bootstrap --platform iOS --cache-builds --use-netrc`. (wcarthage.sh is a temporary replacement for `carthage` to work around [a linker error in Xcode 12](https://github.com/Carthage/Carthage/issues/3019).) -1. Follow the rest of [Carthage’s iOS integration instructions](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos). Your application target’s Embed Frameworks build phase should include `MapboxCoreNavigation.framework`, `MapboxNavigationNative.framework`, and `MapboxCommon.framework`. +1. Follow the rest of [Carthage’s iOS integration instructions](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos). Your application target’s Embed Frameworks build phase should include `MapboxCoreNavigation.framework`, `MapboxNavigationNative.framework`, `MapboxCommon.framework`, and `MapboxAccounts.framework`. + +### Using Swift Package Manager + +To install the MapboxCoreNavigation framework using [Swift Package Manager](https://swift.org/package-manager/) on the command line: + +1. Go to your [Mapbox account dashboard](https://account.mapbox.com/) and create an access token that has the `DOWNLOADS:READ` scope. **PLEASE NOTE: This is not the same as your production Mapbox API token. Make sure to keep it private and do not insert it into any Info.plist file.** Create a file named `.netrc` in your home directory if it doesn’t already exist, then add the following lines to the end of the file: + ``` + machine api.mapbox.com + login mapbox + password PRIVATE_MAPBOX_API_TOKEN + ``` + where _PRIVATE_MAPBOX_API_TOKEN_ is your Mapbox API token with the `DOWNLOADS:READ` scope. + +1. Run `swift package init` to create a Package.swift, then add the following dependency: + ```swift + // Latest stable release + .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.4.0") + // Latest prerelease + .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.4.0") + ``` + +### Using Xcode + +To install the MapboxCoreNavigation framework using [Swift Package Manager](https://swift.org/package-manager/) within Xcode: + +1. Go to your [Mapbox account dashboard](https://account.mapbox.com/) and create an access token that has the `DOWNLOADS:READ` scope. **PLEASE NOTE: This is not the same as your production Mapbox API token. Make sure to keep it private and do not insert it into any Info.plist file.** Create a file named `.netrc` in your home directory if it doesn’t already exist, then add the following lines to the end of the file: + ``` + machine api.mapbox.com + login mapbox + password PRIVATE_MAPBOX_API_TOKEN + ``` + where _PRIVATE_MAPBOX_API_TOKEN_ is your Mapbox API token with the `DOWNLOADS:READ` scope. + +1. In Xcode, go to File ‣ Swift Packages ‣ Add Package Dependency. + +1. Enter `https://github.com/mapbox/mapbox-navigation-ios.git` as the package repository and click Next. + +1. Set Rules to Version, Up to Next Major, and enter `1.4.0` as the minimum version requirement. Click Next. diff --git a/scripts/convert_string_files.sh b/scripts/convert_string_files.sh index 3553259aa78..8f078de53bd 100755 --- a/scripts/convert_string_files.sh +++ b/scripts/convert_string_files.sh @@ -3,7 +3,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source "${DIR}/file_conversion.sh" -DIRECTORIES=( "${DIR}/../MapboxNavigation" "${DIR}/../MapboxCoreNavigation" "${DIR}/../Example" ) +DIRECTORIES=( "${DIR}/../Sources/MapboxNavigation" "${DIR}/../Sources/MapboxCoreNavigation" "${DIR}/../Example" ) for dir in ${DIRECTORIES[@]} do diff --git a/scripts/update-version.sh b/scripts/update-version.sh index 42c7dce63ba..49422dd2b22 100755 --- a/scripts/update-version.sh +++ b/scripts/update-version.sh @@ -53,7 +53,7 @@ sed -i '' -E "s/## *main/## ${SHORT_VERSION}/g" CHANGELOG.md # Skip updating the installation instructions for patch releases or prereleases. if [[ $SHORT_VERSION == $SEM_VERSION && $SHORT_VERSION == *.0 ]]; then step "Updating readmes to version ${SEM_VERSION}…" - sed -i '' -E "s/~> *[^']+/~> ${MINOR_VERSION}/g; s/from: \"*[^\"]+/from: \"${SEM_VERSION}/g; s/\`[^\`]+\` as the minimum version/`${SEM_VERSION}` as the minimum version/g" README.md custom-navigation.md + sed -i '' -E "s/~> *[^']+/~> ${MINOR_VERSION}/g; s/from: \"*[^\"]+/from: \"${SEM_VERSION}/g; s/\`[^\`]+\` as the minimum version/\`${SEM_VERSION}\` as the minimum version/g" README.md custom-navigation.md elif [[ $SHORT_VERSION != $SEM_VERSION ]]; then step "Updating readmes to version ${SEM_VERSION}…" sed -i '' -E "s/:tag => 'v[^']+'/:tag => 'v${SEM_VERSION}'/g; s/\"mapbox\/mapbox-navigation-ios\" \"v[^\"]+\"/\"mapbox\/mapbox-navigation-ios\" \"v${SEM_VERSION}\"/g; s/\.exact\\(\"*[^\"]+/.exact(\"${SEM_VERSION}/g" README.md custom-navigation.md