From 0e0aed446ce8431fc3cd43df02ce4a5d016969f0 Mon Sep 17 00:00:00 2001 From: Anton Makouski <85622715+AntonM030481@users.noreply.github.com> Date: Tue, 9 Aug 2022 09:53:47 +0300 Subject: [PATCH] [ios] Red for over speed; Speed limit for Carplay (#3047) * [ios] Red for over speed; Speed limit for Carplay iPhone: - Red speed digits for overspeed. CarPlay: - Show speed limit. - Red speed digits for overspeed. - Blinking for speed cam. Continuation of refactoring to unify all speed conversions and related localizations. Use MeterPerSecond everywhere. Use numeric speed instead of string. Rename of SpeedLimit to SpeedCamLimit to not confuse with usual limits (not speed cam). Signed-off-by: Anton Makouski * Changes for review Signed-off-by: Anton Makouski --- android/jni/com/mapswithme/maps/Framework.cpp | 4 +- iphone/CoreApi/CoreApi/Common/MWMGeoUtil.h | 11 ++ iphone/CoreApi/CoreApi/Common/MWMGeoUtil.mm | 48 ++++++ iphone/Maps/Categories/UIColor+MapsMeColor.h | 2 +- iphone/Maps/Categories/UIColor+MapsMeColor.m | 2 +- .../Maps/Classes/CarPlay/CarPlayRouter.swift | 62 ++++---- .../Maps/Classes/CarPlay/CarPlayService.swift | 109 +++++++------- .../CarPlay/Templates Data/RouteInfo.swift | 14 +- .../MWMNavigationDashboardEntity.h | 31 ++-- .../MWMNavigationDashboardEntity.mm | 73 ---------- .../MWMNavigationDashboardManager+Entity.mm | 33 ++++- .../Views/NavigationControlView.swift | 38 +++-- .../Views/RoutePreview/MWMRoutePreview.h | 3 - .../Views/RoutePreview/MWMRoutePreview.mm | 2 - .../BaseRoutePreviewStatus.swift | 2 +- .../Core/Framework/MWMFrameworkObservers.h | 2 +- .../ProxyObjects/Routing/MWMRoutingManager.h | 2 +- .../ProxyObjects/Routing/MWMRoutingManager.mm | 22 ++- iphone/Maps/Core/Routing/MWMRouter.h | 2 + iphone/Maps/Core/Routing/MWMRouter.mm | 6 + iphone/Maps/Maps.xcodeproj/project.pbxproj | 4 - .../UI/CarPlay/CarPlayMapViewController.swift | 137 ++++++++++++------ .../Layouts/PlacePageCommonLayout.swift | 19 ++- .../Storyboard/CarPlayStoryboard.storyboard | 8 +- map/routing_manager.cpp | 4 +- map/routing_manager.hpp | 2 +- platform/localization.cpp | 23 ++- platform/localization.hpp | 7 + platform/measurement_utils.cpp | 15 +- platform/measurement_utils.hpp | 3 +- qt/draw_widget.cpp | 2 +- routing/following_info.hpp | 7 +- routing/routing_session.cpp | 18 +-- routing/routing_session.hpp | 2 +- 34 files changed, 403 insertions(+), 316 deletions(-) delete mode 100644 iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.mm diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index e6efa7c15258e..68b1be688310e 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -1202,7 +1202,7 @@ Java_com_mapswithme_maps_Framework_nativeGetRouteFollowingInfo(JNIEnv * env, jcl } auto const & rm = frm()->GetRoutingManager(); - auto const isSpeedLimitExceeded = rm.IsRoutingActive() ? rm.IsSpeedLimitExceeded() : false; + auto const isSpeedCamLimitExceeded = rm.IsRoutingActive() ? rm.IsSpeedCamLimitExceeded() : false; auto const shouldPlaySignal = frm()->GetRoutingManager().GetSpeedCamManager().ShouldPlayBeepSignal(); jobject const result = env->NewObject( klass, ctorRouteInfoID, jni::ToJavaString(env, info.m_distToTarget), @@ -1210,7 +1210,7 @@ Java_com_mapswithme_maps_Framework_nativeGetRouteFollowingInfo(JNIEnv * env, jcl jni::ToJavaString(env, info.m_turnUnitsSuffix), jni::ToJavaString(env, info.m_sourceName), jni::ToJavaString(env, info.m_displayedStreetName), info.m_completionPercent, info.m_turn, info.m_nextTurn, info.m_pedestrianTurn, info.m_exitNum, info.m_time, jLanes, - static_cast(isSpeedLimitExceeded), static_cast(shouldPlaySignal)); + static_cast(isSpeedCamLimitExceeded), static_cast(shouldPlaySignal)); ASSERT(result, (jni::DescribeException())); return result; } diff --git a/iphone/CoreApi/CoreApi/Common/MWMGeoUtil.h b/iphone/CoreApi/CoreApi/Common/MWMGeoUtil.h index 127e37e4a0ef8..a1d00e0ea88a3 100644 --- a/iphone/CoreApi/CoreApi/Common/MWMGeoUtil.h +++ b/iphone/CoreApi/CoreApi/Common/MWMGeoUtil.h @@ -3,6 +3,17 @@ NS_ASSUME_NONNULL_BEGIN +@interface Measure : NSObject + +@property(nonatomic, readonly) double value; +@property(nonatomic, readonly) NSString* valueAsString; + +@property(nonatomic, readonly) NSString* unit; + +- (instancetype) initAsSpeed:(double) mps; + +@end + NS_SWIFT_NAME(GeoUtil) @interface MWMGeoUtil : NSObject diff --git a/iphone/CoreApi/CoreApi/Common/MWMGeoUtil.mm b/iphone/CoreApi/CoreApi/Common/MWMGeoUtil.mm index 61c725f9d65f4..30bf92cd7cf64 100644 --- a/iphone/CoreApi/CoreApi/Common/MWMGeoUtil.mm +++ b/iphone/CoreApi/CoreApi/Common/MWMGeoUtil.mm @@ -3,6 +3,54 @@ #include "geometry/mercator.hpp" #include "geometry/angles.hpp" +#include "platform/localization.hpp" +#include "platform/settings.hpp" +#include "platform/measurement_utils.hpp" + +@implementation Measure + +// Alternative native implementation. +// It has the issue: some localized unit are too long even in .short style. E.g. speed for RU. +/* + let imperial = Settings.measurementUnits() == .imperial + var speedMeasurement = Measurement(value: speed, unit: UnitSpeed.metersPerSecond) + speedMeasurement.convert(to: imperial ? UnitSpeed.milesPerHour : UnitSpeed.kilometersPerHour) + + let formatter = MeasurementFormatter() + formatter.unitOptions = .providedUnit + formatter.numberFormatter.maximumFractionDigits = 0 + formatter.unitStyle = .short + + if speedMeasurement.value < 10 + { + formatter.numberFormatter.minimumFractionDigits = 1 + formatter.numberFormatter.maximumFractionDigits = 1 + } + + let speedString = formatter.string(from: speedMeasurement) +*/ + +- (NSString*) valueAsString { + if (self.value > 9.999) + return [NSString stringWithFormat:@"%.0f", self.value]; + else + return [NSString stringWithFormat:@"%.1f", self.value]; +} + +- (instancetype)initAsSpeed:(double) mps { + self = [super init]; + if (self) { + auto units = measurement_utils::Units::Metric; + settings::TryGet(settings::kMeasurementUnits, units); + _value = measurement_utils::MpsToUnits(mps, units); + + _unit = @(platform::GetLocalizedSpeedUnits(units).c_str()); + } + return self; +} + +@end + @implementation MWMGeoUtil + (float)angleAtPoint:(CLLocationCoordinate2D)p1 toPoint:(CLLocationCoordinate2D)p2 { diff --git a/iphone/Maps/Categories/UIColor+MapsMeColor.h b/iphone/Maps/Categories/UIColor+MapsMeColor.h index e699aff13b7cc..e1ec9b13141b4 100644 --- a/iphone/Maps/Categories/UIColor+MapsMeColor.h +++ b/iphone/Maps/Categories/UIColor+MapsMeColor.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN + (UIColor *)opentableBackground; + (UIColor *)transparentGreen; + (UIColor *)speedLimitRed; -+ (UIColor *)speedLimitGeen; ++ (UIColor *)speedLimitGreen; + (UIColor *)speedLimitWhite; + (UIColor *)speedLimitLightGray; + (UIColor *)speedLimitDarkGray; diff --git a/iphone/Maps/Categories/UIColor+MapsMeColor.m b/iphone/Maps/Categories/UIColor+MapsMeColor.m index 48b8fd0036b97..4571dab46669d 100644 --- a/iphone/Maps/Categories/UIColor+MapsMeColor.m +++ b/iphone/Maps/Categories/UIColor+MapsMeColor.m @@ -259,7 +259,7 @@ + (UIColor *)speedLimitRed { return [UIColor colorWithRed:scaled(224) green:scaled(31) blue:scaled(31) alpha:alpha100]; } -+ (UIColor *)speedLimitGeen { ++ (UIColor *)speedLimitGreen { return [UIColor colorWithRed:scaled(1) green:scaled(104) blue:scaled(44) alpha:alpha100]; } diff --git a/iphone/Maps/Classes/CarPlay/CarPlayRouter.swift b/iphone/Maps/Classes/CarPlay/CarPlayRouter.swift index 79681c275f1a1..892def0d40eb6 100644 --- a/iphone/Maps/Classes/CarPlay/CarPlayRouter.swift +++ b/iphone/Maps/Classes/CarPlay/CarPlayRouter.swift @@ -22,29 +22,29 @@ final class CarPlayRouter: NSObject { var speedCameraMode: SpeedCameraManagerMode { return RoutingManager.routingManager.speedCameraMode } - + override init() { listenerContainer = ListenerContainer() initialSpeedCamSettings = RoutingManager.routingManager.speedCameraMode super.init() } - + func addListener(_ listener: CarPlayRouterListener) { listenerContainer.addListener(listener) } - + func removeListener(_ listener: CarPlayRouterListener) { listenerContainer.removeListener(listener) } - + func subscribeToEvents() { RoutingManager.routingManager.add(self) } - + func unsubscribeFromEvents() { RoutingManager.routingManager.remove(self) } - + func completeRouteAndRemovePoints() { let manager = RoutingManager.routingManager manager.stopRoutingAndRemoveRoutePoints(true) @@ -52,7 +52,7 @@ final class CarPlayRouter: NSObject { manager.apply(routeType: .vehicle) previewTrip = nil } - + func rebuildRoute() { guard let trip = previewTrip else { return } do { @@ -64,7 +64,7 @@ final class CarPlayRouter: NSObject { }) } } - + func buildRoute(trip: CPTrip) { completeRouteAndRemovePoints() previewTrip = trip @@ -87,11 +87,11 @@ final class CarPlayRouter: NSObject { }) return } - + let manager = RoutingManager.routingManager manager.add(routePoint: startPoint) manager.add(routePoint: endPoint) - + do { try manager.buildRoute() } catch let error as NSError { @@ -101,7 +101,7 @@ final class CarPlayRouter: NSObject { }) } } - + func updateStartPointAndRebuild(trip: CPTrip) { let manager = RoutingManager.routingManager previewTrip = trip @@ -128,27 +128,27 @@ final class CarPlayRouter: NSObject { }) } } - + func startRoute() { let manager = RoutingManager.routingManager manager.startRoute() } - + func setupCarPlaySpeedCameraMode() { if case .auto = initialSpeedCamSettings { RoutingManager.routingManager.speedCameraMode = .always } } - + func setupInitialSpeedCameraMode() { RoutingManager.routingManager.speedCameraMode = initialSpeedCamSettings } - + func updateSpeedCameraMode(_ mode: SpeedCameraManagerMode) { initialSpeedCamSettings = mode RoutingManager.routingManager.speedCameraMode = mode } - + func restoreTripPreviewOnCarplay(beforeRootTemplateDidAppear: Bool) { guard MWMRouter.isRestoreProcessCompleted() else { DispatchQueue.main.async { [weak self] in @@ -182,7 +182,7 @@ final class CarPlayRouter: NSObject { CarPlayService.shared.preparePreview(trips: [trip]) } } - + func restoredNavigationSession() -> (CPTrip, RouteInfo)? { let manager = RoutingManager.routingManager if manager.isOnRoute, @@ -211,21 +211,21 @@ extension CarPlayRouter { self?.updateUpcomingManeuvers() } } - + func cancelTrip() { routeSession?.cancelTrip() routeSession = nil completeRouteAndRemovePoints() RoutingManager.routingManager.resetOnNewTurnCallback() } - + func finishTrip() { routeSession?.finishTrip() routeSession = nil completeRouteAndRemovePoints() RoutingManager.routingManager.resetOnNewTurnCallback() } - + func updateUpcomingManeuvers() { let maneuvers = createUpcomingManeuvers() routeSession?.upcomingManeuvers = maneuvers @@ -249,7 +249,7 @@ extension CarPlayRouter { let measurement = Measurement(value: distance, unit: routeInfo.turnUnits) return CPTravelEstimates(distanceRemaining: measurement, timeRemaining: 0.0) } - + private func createUpcomingManeuvers() -> [CPManeuver] { guard let routeInfo = RoutingManager.routingManager.routeInfo else { return [] @@ -286,7 +286,7 @@ extension CarPlayRouter { } return maneuvers } - + func createTrip(startPoint: MWMRoutePoint, endPoint: MWMRoutePoint, routeInfo: RouteInfo? = nil) -> CPTrip { let startPlacemark = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: startPoint.latitude, longitude: startPoint.longitude)) @@ -296,10 +296,10 @@ extension CarPlayRouter { let startItem = MKMapItem(placemark: startPlacemark) let endItem = MKMapItem(placemark: endPlacemark) endItem.name = endPoint.title - + let routeChoice = CPRouteChoice(summaryVariants: [" "], additionalInformationVariants: [], selectionSummaryVariants: []) routeChoice.userInfo = routeInfo - + let trip = CPTrip(origin: startItem, destination: endItem, routeChoices: [routeChoice]) trip.userInfo = [CPConstants.Trip.start: startPoint, CPConstants.Trip.end: endPoint] return trip @@ -308,10 +308,10 @@ extension CarPlayRouter { // MARK: - RoutingManagerListener implementation extension CarPlayRouter: RoutingManagerListener { - func updateCameraInfo(isCameraOnRoute: Bool, speedLimit limit: String?) { - CarPlayService.shared.updateCameraUI(isCameraOnRoute: isCameraOnRoute, speedLimit: limit) + func updateCameraInfo(isCameraOnRoute: Bool, speedLimitMps limit: Double) { + CarPlayService.shared.updateCameraUI(isCameraOnRoute: isCameraOnRoute, speedLimitMps: limit < 0 ? nil : limit) } - + func processRouteBuilderEvent(with code: RouterResultCode, countries: [String]) { guard let trip = previewTrip else { return @@ -345,10 +345,10 @@ extension CarPlayRouter: RoutingManagerListener { }) } } - + func didLocationUpdate(_ notifications: [String]) { guard let trip = previewTrip else { return } - + let manager = RoutingManager.routingManager if manager.isRouteFinished { listenerContainer.forEach({ @@ -356,13 +356,13 @@ extension CarPlayRouter: RoutingManagerListener { }) return } - + guard let routeInfo = manager.routeInfo, manager.isRoutingActive else { return } listenerContainer.forEach({ $0.didUpdateRouteInfo(routeInfo, forTrip: trip) }) - + let tts = MWMTextToSpeech.tts()! if manager.isOnRoute && tts.active { tts.playTurnNotifications(notifications) diff --git a/iphone/Maps/Classes/CarPlay/CarPlayService.swift b/iphone/Maps/Classes/CarPlay/CarPlayService.swift index 3eb34b09a8d27..7e07b67e9c6e4 100644 --- a/iphone/Maps/Classes/CarPlay/CarPlayService.swift +++ b/iphone/Maps/Classes/CarPlay/CarPlayService.swift @@ -31,7 +31,7 @@ final class CarPlayService: NSObject { } var preparedToPreviewTrips: [CPTrip] = [] var isUserPanMap: Bool = false - + @objc func setup(window: CPWindow, interfaceController: CPInterfaceController) { isCarplayActivated = true self.window = window @@ -63,7 +63,7 @@ final class CarPlayService: NSObject { ThemeManager.invalidate() FrameworkHelper.updatePositionArrowOffset(false, offset: 5) } - + @objc func destroy() { if let carplayVC = carplayVC { carplayVC.removeMapView() @@ -89,7 +89,7 @@ final class CarPlayService: NSObject { ThemeManager.invalidate() FrameworkHelper.updatePositionArrowOffset(true, offset: 0) } - + @objc func interfaceStyle() -> UIUserInterfaceStyle { if let window = window, window.traitCollection.userInterfaceIdiom == .carPlay { @@ -97,7 +97,7 @@ final class CarPlayService: NSObject { } return .unspecified } - + private func applyRootViewController() { guard let window = window else { return } let carplaySotyboard = UIStoryboard.instance(.carPlay) @@ -110,14 +110,14 @@ final class CarPlayService: NSObject { mapVC.add(self) } } - + private func applyBaseRootTemplate() { let mapTemplate = MapTemplateBuilder.buildBaseTemplate(positionMode: currentPositionMode) mapTemplate.mapDelegate = self interfaceController?.setRootTemplate(mapTemplate, animated: true) FrameworkHelper.rotateMap(0.0, animated: false) } - + private func applyNavigationRootTemplate(trip: CPTrip, routeInfo: RouteInfo) { let mapTemplate = MapTemplateBuilder.buildNavigationTemplate() mapTemplate.mapDelegate = self @@ -126,13 +126,13 @@ final class CarPlayService: NSObject { if let estimates = createEstimates(routeInfo: routeInfo) { mapTemplate.updateEstimates(estimates, for: trip) } - + if let carplayVC = carplayVC { - carplayVC.updateCurrentSpeed(routeInfo.speed) + carplayVC.updateCurrentSpeed(routeInfo.speedMps, speedLimitMps: routeInfo.speedLimitMps) carplayVC.showSpeedControl() } } - + func pushTemplate(_ templateToPush: CPTemplate, animated: Bool) { if let interfaceController = interfaceController { switch templateToPush { @@ -148,16 +148,16 @@ final class CarPlayService: NSObject { interfaceController.pushTemplate(templateToPush, animated: animated) } } - + func popTemplate(animated: Bool) { interfaceController?.popTemplate(animated: animated) } - + func presentAlert(_ template: CPAlertTemplate, animated: Bool) { interfaceController?.dismissTemplate(animated: false) interfaceController?.presentTemplate(template, animated: animated) } - + func cancelCurrentTrip() { router?.cancelTrip() if let carplayVC = carplayVC { @@ -165,14 +165,13 @@ final class CarPlayService: NSObject { } updateMapTemplateUIToBase() } - - func updateCameraUI(isCameraOnRoute: Bool, speedLimit limit: String?) { + + func updateCameraUI(isCameraOnRoute: Bool, speedLimitMps limit: Double?) { if let carplayVC = carplayVC { - let speedLimit = limit == nil ? nil : Int(limit!) - carplayVC.updateCameraInfo(isCameraOnRoute: isCameraOnRoute, speedLimit: speedLimit) + carplayVC.updateCameraInfo(isCameraOnRoute: isCameraOnRoute, speedLimitMps: limit) } } - + func updateMapTemplateUIToBase() { guard let mapTemplate = rootMapTemplate else { return @@ -188,7 +187,7 @@ final class CarPlayService: NSObject { updateVisibleViewPortState(.default) FrameworkHelper.rotateMap(0.0, animated: true) } - + func updateMapTemplateUIToTripFinished(_ trip: CPTrip) { guard let mapTemplate = rootMapTemplate else { return @@ -206,7 +205,7 @@ final class CarPlayService: NSObject { if let address = trip.destination.placemark.postalAddress?.street { subtitle = subtitle + "\n" + address } - + let alert = CPNavigationAlert(titleVariants: [L("trip_finished")], subtitleVariants: [subtitle], imageSet: nil, @@ -215,18 +214,18 @@ final class CarPlayService: NSObject { duration: 0) mapTemplate.present(navigationAlert: alert, animated: true) } - + func updateVisibleViewPortState(_ state: CPViewPortState) { guard let carplayVC = carplayVC else { return } carplayVC.updateVisibleViewPortState(state) } - + func updateRouteAfterChangingSettings() { router?.rebuildRoute() } - + @objc func showNoMapAlert() { guard let mapTemplate = interfaceController?.topTemplate as? CPMapTemplate, let info = mapTemplate.userInfo as? MapInfo, @@ -237,7 +236,7 @@ final class CarPlayService: NSObject { alert.userInfo = [CPConstants.TemplateKey.alert: CPConstants.TemplateType.downloadMap] presentAlert(alert, animated: true) } - + @objc func hideNoMapAlert() { if let presentedTemplate = interfaceController?.presentedTemplate, let info = presentedTemplate.userInfo as? [String: String], @@ -267,7 +266,7 @@ extension CarPlayService: CPInterfaceControllerDelegate { break } } - + func templateDidAppear(_ aTemplate: CPTemplate, animated: Bool) { guard let mapTemplate = aTemplate as? CPMapTemplate, let info = aTemplate.userInfo as? MapInfo else { @@ -278,12 +277,12 @@ extension CarPlayService: CPInterfaceControllerDelegate { preparedToPreviewTrips = [] return } - + if info.type == CPConstants.TemplateType.preview, let trips = info.trips { showPreview(mapTemplate: mapTemplate, trips: trips) } } - + func templateWillDisappear(_ aTemplate: CPTemplate, animated: Bool) { guard let info = aTemplate.userInfo as? MapInfo else { return @@ -292,7 +291,7 @@ extension CarPlayService: CPInterfaceControllerDelegate { router?.completeRouteAndRemovePoints() } } - + func templateDidDisappear(_ aTemplate: CPTemplate, animated: Bool) { guard !preparedToPreviewTrips.isEmpty, let info = aTemplate.userInfo as? [String: String], @@ -310,7 +309,7 @@ extension CarPlayService: CPInterfaceControllerDelegate { extension CarPlayService: CPSessionConfigurationDelegate { func sessionConfiguration(_ sessionConfiguration: CPSessionConfiguration, limitedUserInterfacesChanged limitedUserInterfaces: CPLimitableUserInterface) { - + } @available(iOS 13.0, *) func sessionConfiguration(_ sessionConfiguration: CPSessionConfiguration, @@ -326,7 +325,7 @@ extension CarPlayService: CPMapTemplateDelegate { MapTemplateBuilder.configurePanUI(mapTemplate: mapTemplate) FrameworkHelper.stopLocationFollow() } - + public func mapTemplateDidDismissPanningInterface(_ mapTemplate: CPMapTemplate) { if let info = mapTemplate.userInfo as? MapInfo, info.type == CPConstants.TemplateType.navigation { @@ -336,7 +335,7 @@ extension CarPlayService: CPMapTemplateDelegate { } FrameworkHelper.switchMyPositionMode() } - + func mapTemplate(_ mapTemplate: CPMapTemplate, panEndedWith direction: CPMapTemplate.PanDirection) { var offset = UIOffset(horizontal: 0.0, vertical: 0.0) let offsetStep: CGFloat = 0.25 @@ -347,7 +346,7 @@ extension CarPlayService: CPMapTemplateDelegate { FrameworkHelper.moveMap(offset) isUserPanMap = true } - + func mapTemplate(_ mapTemplate: CPMapTemplate, panWith direction: CPMapTemplate.PanDirection) { var offset = UIOffset(horizontal: 0.0, vertical: 0.0) let offsetStep: CGFloat = 0.1 @@ -358,7 +357,7 @@ extension CarPlayService: CPMapTemplateDelegate { FrameworkHelper.moveMap(offset) isUserPanMap = true } - + func mapTemplate(_ mapTemplate: CPMapTemplate, startedTrip trip: CPTrip, using routeChoice: CPRouteChoice) { guard let info = routeChoice.userInfo as? RouteInfo else { if let info = routeChoice.userInfo as? [String: Any], @@ -370,13 +369,13 @@ extension CarPlayService: CPMapTemplateDelegate { } mapTemplate.userInfo = MapInfo(type: CPConstants.TemplateType.previewAccepted) mapTemplate.hideTripPreviews() - + guard let router = router, let interfaceController = interfaceController, let rootMapTemplate = rootMapTemplate else { return } - + MapTemplateBuilder.configureNavigationUI(mapTemplate: rootMapTemplate) if interfaceController.templates.count > 1 { @@ -387,14 +386,14 @@ extension CarPlayService: CPMapTemplateDelegate { if let estimates = createEstimates(routeInfo: info) { rootMapTemplate.updateEstimates(estimates, for: trip) } - + if let carplayVC = carplayVC { - carplayVC.updateCurrentSpeed(info.speed) + carplayVC.updateCurrentSpeed(info.speedMps, speedLimitMps: info.speedLimitMps) carplayVC.showSpeedControl() } updateVisibleViewPortState(.navigation) } - + func mapTemplate(_ mapTemplate: CPMapTemplate, displayStyleFor maneuver: CPManeuver) -> CPManeuverDisplayStyle { if let type = maneuver.userInfo as? String, type == CPConstants.Maneuvers.secondary { @@ -402,7 +401,7 @@ extension CarPlayService: CPMapTemplateDelegate { } return .leadingSymbol } - + func mapTemplate(_ mapTemplate: CPMapTemplate, selectedPreviewFor trip: CPTrip, using routeChoice: CPRouteChoice) { @@ -481,7 +480,7 @@ extension CarPlayService: CPSearchTemplateDelegate { completionHandler(items) }) } - + func searchTemplate(_ searchTemplate: CPSearchTemplate, selectedResult item: CPListItem, completionHandler: @escaping () -> Void) { searchService?.saveLastQuery() if let info = item.userInfo as? ListItemInfo, @@ -504,10 +503,10 @@ extension CarPlayService: CarPlayRouterListener { currentTemplate.updateEstimates(estimates, for: trip) } } - + func didUpdateRouteInfo(_ routeInfo: RouteInfo, forTrip trip: CPTrip) { if let carplayVC = carplayVC { - carplayVC.updateCurrentSpeed(routeInfo.speed) + carplayVC.updateCurrentSpeed(routeInfo.speedMps, speedLimitMps: routeInfo.speedLimitMps) } guard let router = router, let template = rootMapTemplate else { @@ -519,13 +518,13 @@ extension CarPlayService: CarPlayRouterListener { } trip.routeChoices.first?.userInfo = routeInfo } - + func didFailureBuildRoute(forTrip trip: CPTrip, code: RouterResultCode, countries: [String]) { guard let template = interfaceController?.topTemplate as? CPMapTemplate else { return } trip.routeChoices.first?.userInfo = [CPConstants.Trip.errorCode: code, CPConstants.Trip.missedCountries: countries] applyUndefinedEstimates(template: template, trip: trip) } - + func routeDidFinish(_ trip: CPTrip) { if router?.currentTrip == nil { return } router?.finishTrip() @@ -558,7 +557,7 @@ extension CarPlayService: LocationModeListener { rootMapTemplate.leadingNavigationBarButtons = [] } } - + func processMyPositionPendingTimeout() { } } @@ -589,7 +588,7 @@ extension CarPlayService { } } } - + func preparePreview(forBookmark bookmark: MWMCarPlayBookmarkObject) { if let router = router, let startPoint = MWMRoutePoint(lastLocationAndType: .start, @@ -607,7 +606,7 @@ extension CarPlayService { } } } - + func preparePreview(trips: [CPTrip]) { let mapTemplate = MapTemplateBuilder.buildTripPreviewTemplate(forTrips: trips) if let interfaceController = interfaceController { @@ -619,14 +618,14 @@ extension CarPlayService { interfaceController.pushTemplate(mapTemplate, animated: false) } } - + func showPreview(mapTemplate: CPMapTemplate, trips: [CPTrip]) { let tripTextConfig = CPTripPreviewTextConfiguration(startButtonTitle: L("trip_start"), additionalRoutesButtonTitle: nil, overviewButtonTitle: nil) mapTemplate.showTripPreviews(trips, textConfiguration: tripTextConfig) } - + func createEstimates(routeInfo: RouteInfo) -> CPTravelEstimates? { if let distance = Double(routeInfo.targetDistance) { let measurement = Measurement(value: distance, @@ -637,7 +636,7 @@ extension CarPlayService { } return nil } - + func applyUndefinedEstimates(template: CPMapTemplate, trip: CPTrip) { let measurement = Measurement(value: -1, unit: UnitLength.meters) @@ -645,7 +644,7 @@ extension CarPlayService { timeRemaining: -1) template.updateEstimates(estimates, for: trip) } - + func showRerouteAlert(trips: [CPTrip]) { let yesAction = CPAlertAction(title: L("yes"), style: .default, handler: { [unowned self] _ in self.router?.cancelTrip() @@ -660,7 +659,7 @@ extension CarPlayService { alert.userInfo = [CPConstants.TemplateKey.alert: CPConstants.TemplateType.redirectRoute] presentAlert(alert, animated: true) } - + func showKeyboardAlert() { let okAction = CPAlertAction(title: L("ok"), style: .default, handler: { [unowned self] _ in self.interfaceController?.dismissTemplate(animated: true) @@ -668,7 +667,7 @@ extension CarPlayService { let alert = CPAlertTemplate(titleVariants: [L("keyboard_availability_alert")], actions: [okAction]) presentAlert(alert, animated: true) } - + func showErrorAlert(code: RouterResultCode, countries: [String]) { var titleVariants = [String]() switch code { @@ -697,18 +696,18 @@ extension CarPlayService { .transitRouteNotFoundTooLongPedestrian: return } - + let okAction = CPAlertAction(title: L("ok"), style: .cancel, handler: { [unowned self] _ in self.interfaceController?.dismissTemplate(animated: true) }) let alert = CPAlertTemplate(titleVariants: titleVariants, actions: [okAction]) presentAlert(alert, animated: true) } - + func showRecoverRouteAlert(trip: CPTrip, isTypeCorrect: Bool) { let yesAction = CPAlertAction(title: L("ok"), style: .default, handler: { [unowned self] _ in var info = trip.userInfo as? [String: MWMRoutePoint] - + if let startPoint = MWMRoutePoint(lastLocationAndType: .start, intermediateIndex: 0) { info?[CPConstants.Trip.start] = startPoint diff --git a/iphone/Maps/Classes/CarPlay/Templates Data/RouteInfo.swift b/iphone/Maps/Classes/CarPlay/Templates Data/RouteInfo.swift index 3180c815e5b9d..ee645bf8f52fc 100644 --- a/iphone/Maps/Classes/CarPlay/Templates Data/RouteInfo.swift +++ b/iphone/Maps/Classes/CarPlay/Templates Data/RouteInfo.swift @@ -8,9 +8,10 @@ class RouteInfo: NSObject { let turnUnits: UnitLength let turnImageName: String? let nextTurnImageName: String? - let speed: Int + let speedMps: Double + let speedLimitMps: Double? let roundExitNumber: Int - + @objc init(timeToTarget: TimeInterval, targetDistance: String, targetUnits: String, @@ -19,7 +20,8 @@ class RouteInfo: NSObject { turnUnits: String, turnImageName: String?, nextTurnImageName: String?, - speed: Int, + speedMps: Double, + speedLimitMps: Double, roundExitNumber: Int) { self.timeToTarget = timeToTarget self.targetDistance = targetDistance @@ -29,10 +31,12 @@ class RouteInfo: NSObject { self.turnUnits = RouteInfo.unitLength(for: turnUnits) self.turnImageName = turnImageName self.nextTurnImageName = nextTurnImageName - self.speed = speed + self.speedMps = speedMps + // speedLimitMps >= 0 means known limited speed. + self.speedLimitMps = speedLimitMps < 0 ? nil : speedLimitMps self.roundExitNumber = roundExitNumber } - + class func unitLength(for targetUnits: String) -> UnitLength { switch targetUnits { case "mi": diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.h b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.h index 71ef05708d8b3..2639e5df510f9 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.h +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.h @@ -2,25 +2,24 @@ @interface MWMNavigationDashboardEntity : NSObject -@property(copy, nonatomic, readonly) NSArray * transitSteps; -@property(copy, nonatomic, readonly) NSAttributedString * estimate; -@property(copy, nonatomic, readonly) NSAttributedString * estimateDot; -@property(copy, nonatomic, readonly) NSString * distanceToTurn; -@property(copy, nonatomic, readonly) NSString * streetName; -@property(copy, nonatomic, readonly) NSString * targetDistance; -@property(copy, nonatomic, readonly) NSString * targetUnits; -@property(copy, nonatomic, readonly) NSString * turnUnits; -@property(copy, nonatomic, readonly) NSString * speedLimit; +@property(copy, nonatomic, readonly) NSArray *transitSteps; +@property(copy, nonatomic, readonly) NSAttributedString *estimate; +@property(copy, nonatomic, readonly) NSString *distanceToTurn; +@property(copy, nonatomic, readonly) NSString *streetName; +@property(copy, nonatomic, readonly) NSString *targetDistance; +@property(copy, nonatomic, readonly) NSString *targetUnits; +@property(copy, nonatomic, readonly) NSString *turnUnits; +@property(nonatomic, readonly) double speedLimitMps; @property(nonatomic, readonly) BOOL isValid; @property(nonatomic, readonly) CGFloat progress; -@property(nonatomic, readonly) NSString * arrival; -@property(nonatomic, readonly) NSString * eta; -@property(nonatomic, readonly) NSString * speed; -@property(nonatomic, readonly) NSString * speedUnits; @property(nonatomic, readonly) NSUInteger roundExitNumber; -@property(nonatomic, readonly) UIImage * nextTurnImage; -@property(nonatomic, readonly) UIImage * turnImage; -@property(nonatomic, readonly) BOOL isSpeedLimitExceeded; +@property(nonatomic, readonly) NSUInteger timeToTarget; +@property(nonatomic, readonly) UIImage *nextTurnImage; +@property(nonatomic, readonly) UIImage *turnImage; + +@property(nonatomic, readonly) NSString * arrival; + ++ (NSAttributedString *) estimateDot; + (instancetype) new __attribute__((unavailable("init is not available"))); diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.mm deleted file mode 100644 index 2275c782559e9..0000000000000 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.mm +++ /dev/null @@ -1,73 +0,0 @@ -#import "MWMNavigationDashboardEntity.h" -#import "MWMCoreUnits.h" -#import "MWMLocationManager.h" -#import "MWMRouterTransitStepInfo.h" -#import "MWMSettings.h" -#import "SwiftBridge.h" - -#include - -#include "map/routing_manager.hpp" - -#include "platform/localization.hpp" -#include "platform/measurement_utils.hpp" - -@interface MWMNavigationDashboardEntity () - -@property(copy, nonatomic, readwrite) NSArray * transitSteps; -@property(copy, nonatomic, readwrite) NSAttributedString * estimate; -@property(copy, nonatomic, readwrite) NSString * distanceToTurn; -@property(copy, nonatomic, readwrite) NSString * streetName; -@property(copy, nonatomic, readwrite) NSString * targetDistance; -@property(copy, nonatomic, readwrite) NSString * targetUnits; -@property(copy, nonatomic, readwrite) NSString * turnUnits; -@property(copy, nonatomic, readwrite) NSString * speedLimit; -@property(nonatomic, readwrite) BOOL isValid; -@property(nonatomic, readwrite) CGFloat progress; -@property(nonatomic, readwrite) NSUInteger roundExitNumber; -@property(nonatomic, readwrite) NSUInteger timeToTarget; -@property(nonatomic, readwrite) UIImage * nextTurnImage; -@property(nonatomic, readwrite) UIImage * turnImage; - -@end - -@implementation MWMNavigationDashboardEntity - -- (NSString *)speed -{ - CLLocation * lastLocation = [MWMLocationManager lastLocation]; - if (!lastLocation || lastLocation.speed < 0) - return nil; - auto const units = coreUnits([MWMSettings measurementUnits]); - return @(measurement_utils::FormatSpeedNumeric(lastLocation.speed, units).c_str()); -} - -- (BOOL)isSpeedLimitExceeded -{ - return GetFramework().GetRoutingManager().IsSpeedLimitExceeded(); -} - -- (NSString *)speedUnits -{ - return @(platform::GetLocalizedSpeedUnits().c_str()); -} - -- (NSString *)eta { return [NSDateComponentsFormatter etaStringFrom:self.timeToTarget]; } -- (NSString *)arrival -{ - auto arrivalDate = [[NSDate date] dateByAddingTimeInterval:self.timeToTarget]; - return [NSDateFormatter localizedStringFromDate:arrivalDate - dateStyle:NSDateFormatterNoStyle - timeStyle:NSDateFormatterShortStyle]; -} - -- (NSAttributedString *)estimateDot -{ - auto attributes = @{ - NSForegroundColorAttributeName: [UIColor blackSecondaryText], - NSFontAttributeName: [UIFont medium17] - }; - return [[NSAttributedString alloc] initWithString:@" • " attributes:attributes]; -} - -@end diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager+Entity.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager+Entity.mm index 449357cdc558d..f45ef2758e4d8 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager+Entity.mm +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager+Entity.mm @@ -68,11 +68,11 @@ return [UIImage imageNamed:imageName]; } -NSAttributedString *estimate(NSTimeInterval time, NSAttributedString *dot, NSString *distance, NSString *distanceUnits, +NSAttributedString *estimate(NSTimeInterval time, NSString *distance, NSString *distanceUnits, NSDictionary *primaryAttributes, NSDictionary *secondaryAttributes, BOOL isWalk) { NSString *eta = [NSDateComponentsFormatter etaStringFrom:time]; auto result = [[NSMutableAttributedString alloc] initWithString:eta attributes:primaryAttributes]; - [result appendAttributedString:dot]; + [result appendAttributedString:MWMNavigationDashboardEntity.estimateDot]; if (isWalk) { UIFont *font = primaryAttributes[NSFontAttributeName]; @@ -106,7 +106,7 @@ @interface MWMNavigationDashboardEntity () @property(copy, nonatomic, readwrite) NSString *targetDistance; @property(copy, nonatomic, readwrite) NSString *targetUnits; @property(copy, nonatomic, readwrite) NSString *turnUnits; -@property(copy, nonatomic, readwrite) NSString *speedLimit; +@property(nonatomic, readwrite) double speedLimitMps; @property(nonatomic, readwrite) BOOL isValid; @property(nonatomic, readwrite) CGFloat progress; @property(nonatomic, readwrite) NSUInteger roundExitNumber; @@ -116,6 +116,27 @@ @interface MWMNavigationDashboardEntity () @end +@implementation MWMNavigationDashboardEntity + +- (NSString *)arrival +{ + auto arrivalDate = [[NSDate date] dateByAddingTimeInterval:self.timeToTarget]; + return [NSDateFormatter localizedStringFromDate:arrivalDate + dateStyle:NSDateFormatterNoStyle + timeStyle:NSDateFormatterShortStyle]; +} + ++ (NSAttributedString *)estimateDot +{ + auto attributes = @{ + NSForegroundColorAttributeName: [UIColor blackSecondaryText], + NSFontAttributeName: [UIFont medium17] + }; + return [[NSAttributedString alloc] initWithString:@" • " attributes:attributes]; +} + +@end + @interface MWMRouterTransitStepInfo () - (instancetype)initWithStepInfo:(TransitStepInfo const &)info; @@ -150,9 +171,9 @@ - (void)updateFollowingInfo:(routing::FollowingInfo const &)info type:(MWMRouter entity.distanceToTurn = @(info.m_distToTurn.c_str()); entity.turnUnits = [self localizedUnitLength:@(info.m_turnUnitsSuffix.c_str())]; entity.streetName = @(info.m_displayedStreetName.c_str()); - entity.speedLimit = @(info.m_speedLimit.c_str()); + entity.speedLimitMps = info.m_speedLimitMps; - entity.estimate = estimate(entity.timeToTarget, entity.estimateDot, entity.targetDistance, entity.targetUnits, + entity.estimate = estimate(entity.timeToTarget, entity.targetDistance, entity.targetUnits, self.etaAttributes, self.etaSecondaryAttributes, NO); if (type == MWMRouterTypePedestrian) { @@ -178,7 +199,7 @@ - (void)updateTransitInfo:(TransitRouteInfo const &)info { if (auto entity = self.entity) { entity.isValid = YES; entity.estimate = - estimate(info.m_totalTimeInSec, entity.estimateDot, @(info.m_totalPedestrianDistanceStr.c_str()), + estimate(info.m_totalTimeInSec, @(info.m_totalPedestrianDistanceStr.c_str()), @(info.m_totalPedestrianUnitsSuffix.c_str()), self.etaAttributes, self.etaSecondaryAttributes, YES); NSMutableArray *transitSteps = [@[] mutableCopy]; for (auto const &stepInfo : info.m_steps) diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/NavigationControlView.swift b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/NavigationControlView.swift index 11d346856f448..084d5f0c4fe7c 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/NavigationControlView.swift +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/NavigationControlView.swift @@ -136,7 +136,7 @@ final class NavigationControlView: SolidTouchView, MWMTextToSpeechObserver, MapO ] if timePageControl.currentPage == 0 { - timeLabel.text = info.eta + timeLabel.text = DateComponentsFormatter.etaString(from: TimeInterval(info.timeToTarget)) } else { timeLabel.text = info.arrival } @@ -155,23 +155,35 @@ final class NavigationControlView: SolidTouchView, MWMTextToSpeechObserver, MapO } } - var speed = info.speed ?? "0" - if (info.speedLimit != "") { - speed += " / " + info.speedLimit; + var speedMps = 0.0 + if let s = LocationManager.lastLocation()?.speed, s > 0 { + speedMps = s } - + let speedMeasure = Measure(asSpeed: speedMps) + var speed = speedMeasure.valueAsString; + // speedLimitMps >= 0 means known limited speed. + if (info.speedLimitMps >= 0) { + let speedLimitMeasure = Measure(asSpeed: info.speedLimitMps) + // speedLimitMps == 0 means unlimited speed. + speed += " / " + (info.speedLimitMps == 0 ? "∞" : speedLimitMeasure.valueAsString); + } + speedLabel.text = speed - speedLegendLabel.text = info.speedUnits + speedLegendLabel.text = speedMeasure.unit let speedWithLegend = NSMutableAttributedString(string: speed, attributes: routingNumberAttributes) - speedWithLegend.append(NSAttributedString(string: info.speedUnits, attributes: routingLegendAttributes)) + speedWithLegend.append(NSAttributedString(string: speedMeasure.unit, attributes: routingLegendAttributes)) speedWithLegendLabel.attributedText = speedWithLegend - let speedLimitExceeded = info.isSpeedLimitExceeded - let textColor = speedLimitExceeded ? UIColor.white() : UIColor.blackPrimaryText() - speedBackground.backgroundColor = speedLimitExceeded ? UIColor.buttonRed() : UIColor.clear - speedLabel.textColor = textColor - speedLegendLabel.textColor = textColor - speedWithLegendLabel.textColor = textColor + if MWMRouter.isSpeedCamLimitExceeded() { + speedLabel.textColor = UIColor.white() + speedBackground.backgroundColor = UIColor.buttonRed() + } else { + let isSpeedLimitExceeded = info.speedLimitMps > 0 && speedMps > info.speedLimitMps + speedLabel.textColor = isSpeedLimitExceeded ? UIColor.buttonRed() : UIColor.blackPrimaryText() + speedBackground.backgroundColor = UIColor.clear + } + speedLegendLabel.textColor = speedLabel.textColor + speedWithLegendLabel.textColor = speedLabel.textColor routingProgress.constant = progressView.width * info.progress / 100 } diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/MWMRoutePreview.h b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/MWMRoutePreview.h index 507c3ba149160..ce47ab998fde5 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/MWMRoutePreview.h +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/MWMRoutePreview.h @@ -7,9 +7,6 @@ typedef NS_ENUM(NSInteger, MWMDrivingOptionsState) { MWMDrivingOptionsStateChange }; -@class MWMNavigationDashboardEntity; -@class MWMNavigationDashboardManager; -@class MWMTaxiCollectionView; @class MWMRoutePreview; @protocol MWMRoutePreviewDelegate diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/MWMRoutePreview.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/MWMRoutePreview.mm index d07ec79f75247..a0a5b98981b18 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/MWMRoutePreview.mm +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/MWMRoutePreview.mm @@ -1,8 +1,6 @@ #import "MWMRoutePreview.h" #import "MWMCircularProgress.h" #import "MWMLocationManager.h" -#import "MWMNavigationDashboardEntity.h" -#import "MWMNavigationDashboardManager.h" #import "MWMRouter.h" #import "UIButton+Orientation.h" #import "UIImageView+Coloring.h" diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/RoutePreviewStatus/BaseRoutePreviewStatus.swift b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/RoutePreviewStatus/BaseRoutePreviewStatus.swift index 63c0f591b5fc5..8237acfee77b8 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/RoutePreviewStatus/BaseRoutePreviewStatus.swift +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/RoutePreview/RoutePreviewStatus/BaseRoutePreviewStatus.swift @@ -142,7 +142,7 @@ final class BaseRoutePreviewStatus: SolidTouchView { if let result = info.estimate.mutableCopy() as? NSMutableAttributedString { if let elevation = self.elevation { - result.append(info.estimateDot) + result.append(MWMNavigationDashboardEntity.estimateDot()) result.append(elevation) } resultLabel.attributedText = result diff --git a/iphone/Maps/Core/Framework/MWMFrameworkObservers.h b/iphone/Maps/Core/Framework/MWMFrameworkObservers.h index de9b07e573cb1..bf7e7e86548e0 100644 --- a/iphone/Maps/Core/Framework/MWMFrameworkObservers.h +++ b/iphone/Maps/Core/Framework/MWMFrameworkObservers.h @@ -20,7 +20,7 @@ using namespace storage; - (void)processRouteBuilderProgress:(CGFloat)progress; - (void)processRouteRecommendation:(MWMRouterRecommendation)recommendation; -- (void)speedCameraShowedUpOnRoute:(double)speedLimit; +- (void)speedCameraShowedUpOnRoute:(double)speedLimitKMph; - (void)speedCameraLeftVisibleArea; @end diff --git a/iphone/Maps/Core/Framework/ProxyObjects/Routing/MWMRoutingManager.h b/iphone/Maps/Core/Framework/ProxyObjects/Routing/MWMRoutingManager.h index dd243d93b64ea..6d3db0db76d2c 100644 --- a/iphone/Maps/Core/Framework/ProxyObjects/Routing/MWMRoutingManager.h +++ b/iphone/Maps/Core/Framework/ProxyObjects/Routing/MWMRoutingManager.h @@ -11,7 +11,7 @@ NS_SWIFT_NAME(RoutingManagerListener) - (void)processRouteBuilderEventWithCode:(MWMRouterResultCode)code countries:(NSArray *)absentCountries; - (void)didLocationUpdate:(NSArray *)notifications; -- (void)updateCameraInfo:(BOOL)isCameraOnRoute speedLimit:(nullable NSString *)limit NS_SWIFT_NAME(updateCameraInfo(isCameraOnRoute:speedLimit:)); +- (void)updateCameraInfo:(BOOL)isCameraOnRoute speedLimitMps:(double)limit NS_SWIFT_NAME(updateCameraInfo(isCameraOnRoute:speedLimitMps:)); @end NS_SWIFT_NAME(RoutingManager) diff --git a/iphone/Maps/Core/Framework/ProxyObjects/Routing/MWMRoutingManager.mm b/iphone/Maps/Core/Framework/ProxyObjects/Routing/MWMRoutingManager.mm index 5718ae3b77ecd..209b9610b6312 100644 --- a/iphone/Maps/Core/Framework/ProxyObjects/Routing/MWMRoutingManager.mm +++ b/iphone/Maps/Core/Framework/ProxyObjects/Routing/MWMRoutingManager.mm @@ -82,10 +82,9 @@ - (MWMRouteInfo *)routeInfo { self.rm.GetRouteFollowingInfo(info); if (!info.IsValid()) { return nil; } CLLocation * lastLocation = [MWMLocationManager lastLocation]; - NSString *speed = @"0"; + double speedMps = 0; if (lastLocation && lastLocation.speed >= 0) { - auto const units = coreUnits([MWMSettings measurementUnits]); - speed = @(measurement_utils::FormatSpeedNumeric(lastLocation.speed, units).c_str()); + speedMps = lastLocation.speed; } NSInteger roundExitNumber = 0; if (info.m_turn == routing::turns::CarDirection::EnterRoundAbout || @@ -93,7 +92,7 @@ - (MWMRouteInfo *)routeInfo { info.m_turn == routing::turns::CarDirection::LeaveRoundAbout) { roundExitNumber = info.m_exitNum; } - + MWMRouteInfo *objCInfo = [[MWMRouteInfo alloc] initWithTimeToTarget:info.m_time targetDistance:@(info.m_distToTarget.c_str()) targetUnits:@(info.m_targetUnitsSuffix.c_str()) @@ -102,8 +101,9 @@ - (MWMRouteInfo *)routeInfo { turnUnits:@(info.m_turnUnitsSuffix.c_str()) turnImageName:[self turnImageName:info.m_turn isPrimary:YES] nextTurnImageName:[self turnImageName:info.m_nextTurn isPrimary:NO] - speed:[speed integerValue] - roundExitNumber: roundExitNumber]; + speedMps:speedMps + speedLimitMps:info.m_speedLimitMps + roundExitNumber:roundExitNumber]; return objCInfo; } @@ -143,7 +143,7 @@ - (void)saveRoute { - (void)buildRouteWithDidFailError:(NSError * __autoreleasing __nullable *)errorPtr { auto const & points = self.rm.GetRoutePoints(); auto const pointsCount = points.size(); - + if (pointsCount > 1) { self.rm.BuildRoute(); } else { @@ -229,12 +229,10 @@ - (void)speedCameraShowedUpOnRoute:(double)speedLimit { NSArray> * objects = self.listeners.allObjects; for (id object in objects) { if (speedLimit == routing::SpeedCameraOnRoute::kNoSpeedInfo) { - [object updateCameraInfo:YES speedLimit:nil]; + [object updateCameraInfo:YES speedLimitMps:-1]; } else { auto const metersPerSecond = measurement_utils::KmphToMps(speedLimit); - - NSString *limit = @(measurement_utils::FormatSpeed(metersPerSecond).c_str()); - [object updateCameraInfo:YES speedLimit:limit]; + [object updateCameraInfo:YES speedLimitMps:metersPerSecond]; } } } @@ -242,7 +240,7 @@ - (void)speedCameraShowedUpOnRoute:(double)speedLimit { - (void)speedCameraLeftVisibleArea { NSArray> * objects = self.listeners.allObjects; for (id object in objects) { - [object updateCameraInfo:NO speedLimit:nil]; + [object updateCameraInfo:NO speedLimitMps:-1]; } } diff --git a/iphone/Maps/Core/Routing/MWMRouter.h b/iphone/Maps/Core/Routing/MWMRouter.h index b1cfe9dad2f6c..c7a9f0c2c15e2 100644 --- a/iphone/Maps/Core/Routing/MWMRouter.h +++ b/iphone/Maps/Core/Routing/MWMRouter.h @@ -21,6 +21,8 @@ typedef void (^MWMImageHeightBlock)(UIImage *, NSString *, NSString *); + (BOOL)isRouteRebuildingOnly; + (BOOL)isOnRoute; ++ (BOOL)isSpeedCamLimitExceeded; + + (BOOL)canAddIntermediatePoint; + (void)startRouting; diff --git a/iphone/Maps/Core/Routing/MWMRouter.mm b/iphone/Maps/Core/Routing/MWMRouter.mm index 16afd18a4daa2..fb24a9ee27d5e 100644 --- a/iphone/Maps/Core/Routing/MWMRouter.mm +++ b/iphone/Maps/Core/Routing/MWMRouter.mm @@ -88,6 +88,12 @@ + (BOOL)isOnRoute { + (BOOL)IsRouteValid { return GetFramework().GetRoutingManager().IsRouteValid(); } + ++ (BOOL)isSpeedCamLimitExceeded +{ + return GetFramework().GetRoutingManager().IsSpeedCamLimitExceeded(); +} + + (NSArray *)points { NSMutableArray *points = [@[] mutableCopy]; auto const routePoints = GetFramework().GetRoutingManager().GetRoutePoints(); diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index e3e0fcd03be79..af89df77358c7 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -99,7 +99,6 @@ 34AB39C21D2BD8310021857D /* MWMStopButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 34AB39C01D2BD8310021857D /* MWMStopButton.m */; }; 34AB66051FC5AA320078E451 /* MWMNavigationDashboardManager+Entity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 34AB65C51FC5AA320078E451 /* MWMNavigationDashboardManager+Entity.mm */; }; 34AB66081FC5AA320078E451 /* MWMNavigationDashboardManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 34AB65C61FC5AA320078E451 /* MWMNavigationDashboardManager.mm */; }; - 34AB660B1FC5AA320078E451 /* MWMNavigationDashboardEntity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 34AB65CB1FC5AA320078E451 /* MWMNavigationDashboardEntity.mm */; }; 34AB660E1FC5AA320078E451 /* NavigationControlView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 34AB65CD1FC5AA320078E451 /* NavigationControlView.xib */; }; 34AB66111FC5AA320078E451 /* NavigationTurnsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34AB65CE1FC5AA320078E451 /* NavigationTurnsView.swift */; }; 34AB66141FC5AA320078E451 /* MWMNavigationInfoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 34AB65D01FC5AA320078E451 /* MWMNavigationInfoView.xib */; }; @@ -855,7 +854,6 @@ 34AB65C71FC5AA320078E451 /* MWMNavigationDashboardManager+Entity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MWMNavigationDashboardManager+Entity.h"; sourceTree = ""; }; 34AB65C91FC5AA320078E451 /* MWMNavigationDashboardEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMNavigationDashboardEntity.h; sourceTree = ""; }; 34AB65CA1FC5AA320078E451 /* MWMNavigationDashboardManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMNavigationDashboardManager.h; sourceTree = ""; }; - 34AB65CB1FC5AA320078E451 /* MWMNavigationDashboardEntity.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMNavigationDashboardEntity.mm; sourceTree = ""; }; 34AB65CD1FC5AA320078E451 /* NavigationControlView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NavigationControlView.xib; sourceTree = ""; }; 34AB65CE1FC5AA320078E451 /* NavigationTurnsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTurnsView.swift; sourceTree = ""; }; 34AB65CF1FC5AA320078E451 /* MWMNavigationInfoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMNavigationInfoView.h; sourceTree = ""; }; @@ -2267,7 +2265,6 @@ isa = PBXGroup; children = ( 34AB65C91FC5AA320078E451 /* MWMNavigationDashboardEntity.h */, - 34AB65CB1FC5AA320078E451 /* MWMNavigationDashboardEntity.mm */, 34AB65CA1FC5AA320078E451 /* MWMNavigationDashboardManager.h */, 34AB65C61FC5AA320078E451 /* MWMNavigationDashboardManager.mm */, 34AB65C71FC5AA320078E451 /* MWMNavigationDashboardManager+Entity.h */, @@ -4325,7 +4322,6 @@ 993DF10923F6BDB100AC231A /* IFonts.swift in Sources */, 47699A0821F08E37009E6585 /* NSDate+TimeDistance.m in Sources */, 34845DB31E165E24003D55B9 /* SearchNoResultsViewController.swift in Sources */, - 34AB660B1FC5AA320078E451 /* MWMNavigationDashboardEntity.mm in Sources */, 47E3C72B2111E62A008B3B27 /* FadeOutAnimatedTransitioning.swift in Sources */, 471A7BC4248471BE00A0D4C1 /* BookmarkUIUtils.swift in Sources */, ); diff --git a/iphone/Maps/UI/CarPlay/CarPlayMapViewController.swift b/iphone/Maps/UI/CarPlay/CarPlayMapViewController.swift index 12bee7b2f3730..f17cdc5000767 100644 --- a/iphone/Maps/UI/CarPlay/CarPlayMapViewController.swift +++ b/iphone/Maps/UI/CarPlay/CarPlayMapViewController.swift @@ -1,23 +1,25 @@ final class CarPlayMapViewController: MWMViewController { private(set) var mapView: EAGLView? @IBOutlet var speedInfoView: UIView! - @IBOutlet var speedLimitContainer: UIView! + @IBOutlet var speedCamLimitContainer: UIView! @IBOutlet var speedCamImageView: UIImageView! - @IBOutlet var speedLimitLabel: UILabel! + @IBOutlet var speedCamLimitLabel: UILabel! @IBOutlet var currentSpeedView: UIView! @IBOutlet var currentSpeedLabel: UILabel! - private var currentSpeed: Int = 0 - private var speedLimit: Int? + private var currentSpeedMps: Double = 0.0 + private var speedLimitMps: Double? + private var speedCamLimitMps: Double? private var isCameraOnRoute: Bool = false private var viewPortState: CPViewPortState = .default + private var isSpeedCamBlinking: Bool = false private var isLeftWheelCar: Bool { return self.speedInfoView.frame.origin.x > self.view.frame.midX } - + override func viewDidLoad() { super.viewDidLoad() } - + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if mapView?.drapeEngineCreated == false { @@ -25,11 +27,11 @@ final class CarPlayMapViewController: MWMViewController { } updateVisibleViewPortState(viewPortState) } - + func addMapView(_ mapView: EAGLView, mapButtonSafeAreaLayoutGuide: UILayoutGuide) { mapView.translatesAutoresizingMaskIntoConstraints = false removeMapView() - + self.mapView = mapView mapView.frame = view.bounds view.insertSubview(mapView, at: 0) @@ -38,73 +40,124 @@ final class CarPlayMapViewController: MWMViewController { mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true speedInfoView.trailingAnchor.constraint(equalTo: mapButtonSafeAreaLayoutGuide.trailingAnchor).isActive = true + + speedCamLimitContainer.layer.borderWidth = 2.0 } - + func removeMapView() { - if let mapView = mapView { + if let mapView = self.mapView { mapView.removeFromSuperview() self.mapView = nil } } - - + + func hideSpeedControl() { if !speedInfoView.isHidden { speedInfoView.isHidden = true } } - + func showSpeedControl() { if speedInfoView.isHidden { speedInfoView.isHidden = false } } - - func updateCurrentSpeed(_ speed: Int) { - self.currentSpeed = speed + + func updateCurrentSpeed(_ speedMps: Double, speedLimitMps: Double?) { + self.currentSpeedMps = speedMps + self.speedLimitMps = speedLimitMps updateSpeedControl() } - - func updateCameraInfo(isCameraOnRoute: Bool, speedLimit: Int?) { + + func updateCameraInfo(isCameraOnRoute: Bool, speedLimitMps: Double?) { self.isCameraOnRoute = isCameraOnRoute - self.speedLimit = speedLimit + self.speedCamLimitMps = speedLimitMps updateSpeedControl() } - + + private func BlinkSpeedCamLimit(blink: Bool) + { + if blink { + if !isSpeedCamBlinking { + speedCamLimitLabel.alpha = 0 + speedCamImageView.alpha = 1 + UIView.animate(withDuration: 0.5, + delay:0.0, + options:[.repeat, .autoreverse, .curveEaseOut], + animations: { self.speedCamImageView.alpha = 0; self.speedCamLimitLabel.alpha = 1 }) + isSpeedCamBlinking = true + } + } else { + if (isSpeedCamBlinking) { + speedCamLimitLabel.layer.removeAllAnimations() + speedCamImageView.layer.removeAllAnimations() + isSpeedCamBlinking = false + } + } + } + private func updateSpeedControl() { - currentSpeedLabel.text = "\(currentSpeed)" + let speedMeasure = Measure.init(asSpeed: currentSpeedMps) + currentSpeedLabel.text = speedMeasure.valueAsString + if isCameraOnRoute { - speedLimitContainer.layer.borderColor = UIColor.speedLimitRed().cgColor - speedLimitContainer.layer.borderWidth = 2.0 - if let speedLimit = speedLimit { - speedCamImageView.alpha = 0.0 - speedLimitLabel.textColor = UIColor.speedLimitDarkGray() - speedLimitLabel.text = "\(speedLimit)" - speedLimitLabel.alpha = 1.0 + speedCamLimitContainer.layer.borderColor = UIColor.speedLimitRed().cgColor + speedCamImageView.tintColor = UIColor.speedLimitRed() + + // self.speedCamLimitMps comes from SpeedCamManager and is based on + // the nearest speed camera info when it is close enough. + // If it's unknown self.speedLimitMps is used, which is based on current road speed limit. + if let speedCamLimitMps = (self.speedCamLimitMps ?? self.speedLimitMps) { + BlinkSpeedCamLimit(blink: true) + let speedCamLimitMeasure = Measure.init(asSpeed: speedCamLimitMps) + speedCamLimitLabel.text = speedCamLimitMeasure.valueAsString + speedCamLimitLabel.textColor = UIColor.speedLimitDarkGray() + currentSpeedLabel.textColor = UIColor.white - if speedLimit >= currentSpeed { - currentSpeedView.backgroundColor = UIColor.speedLimitGeen() + if speedCamLimitMps >= currentSpeedMps { + currentSpeedView.backgroundColor = UIColor.speedLimitGreen() } else { currentSpeedView.backgroundColor = UIColor.speedLimitRed() } } else { - speedLimitLabel.alpha = 0.0 + BlinkSpeedCamLimit(blink: false) + speedCamLimitLabel.alpha = 0.0 speedCamImageView.tintColor = UIColor.speedLimitRed() speedCamImageView.alpha = 1.0 + currentSpeedLabel.textColor = UIColor.speedLimitDarkGray() currentSpeedView.backgroundColor = UIColor.speedLimitWhite() } - } else { - speedLimitContainer.layer.borderColor = UIColor.speedLimitLightGray().cgColor - speedLimitContainer.layer.borderWidth = 2.0 - speedLimitLabel.alpha = 0.0 - speedCamImageView.tintColor = UIColor.speedLimitLightGray() - speedCamImageView.alpha = 1.0 + } else { // !isCameraOnRoute + BlinkSpeedCamLimit(blink: false) currentSpeedLabel.textColor = UIColor.speedLimitDarkGray() + if let speedLimitMps = self.speedLimitMps { + speedCamImageView.alpha = 0.0 + let speedLimitMeasure = Measure.init(asSpeed: speedLimitMps) + speedCamLimitLabel.textColor = UIColor.speedLimitDarkGray() + // speedLimitMps == 0 means unlimited speed. + if speedLimitMeasure.value == 0 { + speedCamLimitLabel.text = "🚀" //"∞" + } + else { + speedCamLimitLabel.text = speedLimitMeasure.valueAsString; + } + speedCamLimitLabel.alpha = 1.0 + speedCamLimitContainer.layer.borderColor = UIColor.speedLimitRed().cgColor + if currentSpeedMps > speedLimitMps { + currentSpeedLabel.textColor = UIColor.speedLimitRed() + } + } else { + speedCamImageView.tintColor = UIColor.speedLimitLightGray() + speedCamImageView.alpha = 1.0 + speedCamLimitLabel.alpha = 0.0 + speedCamLimitContainer.layer.borderColor = UIColor.speedLimitLightGray().cgColor + } currentSpeedView.backgroundColor = UIColor.speedLimitWhite() } } - + func updateVisibleViewPortState(_ state: CPViewPortState) { viewPortState = state switch viewPortState { @@ -116,7 +169,7 @@ final class CarPlayMapViewController: MWMViewController { updateVisibleViewPortToNavigationState() } } - + private func updateVisibleViewPortToPreviewState() { let viewBounds = view.bounds let previewWidth = self.view.frame.width * 0.45 @@ -128,7 +181,7 @@ final class CarPlayMapViewController: MWMViewController { height: viewBounds.height - origin.y) FrameworkHelper.setVisibleViewport(frame, scaleFactor: mapView?.contentScaleFactor ?? 1) } - + private func updateVisibleViewPortToNavigationState() { let viewBounds = view.bounds let previewWidth = viewBounds.width * 0.45 @@ -141,11 +194,11 @@ final class CarPlayMapViewController: MWMViewController { height: viewBounds.height - origin.y) FrameworkHelper.setVisibleViewport(frame, scaleFactor: mapView?.contentScaleFactor ?? 1) } - + private func updateVisibleViewPortToDefaultState() { FrameworkHelper.setVisibleViewport(view.bounds, scaleFactor: mapView?.contentScaleFactor ?? 1) } - + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) ThemeManager.invalidate() diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageCommonLayout.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageCommonLayout.swift index 7a634a6ac7347..c8a4086f59e1f 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageCommonLayout.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageCommonLayout.swift @@ -36,7 +36,7 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout { vc.placePagePreviewData = placePageData.previewData return vc } () - + lazy var wikiDescriptionViewController: WikiDescriptionViewController = { let vc = storyboard.instantiateViewController(ofType: WikiDescriptionViewController.self) vc.view.isHidden = true @@ -50,14 +50,14 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout { vc.delegate = interactor return vc } () - + lazy var infoViewController: PlacePageInfoViewController = { let vc = storyboard.instantiateViewController(ofType: PlacePageInfoViewController.self) vc.placePageInfoData = placePageData.infoData vc.delegate = interactor return vc } () - + lazy var buttonsViewController: PlacePageButtonsViewController = { let vc = storyboard.instantiateViewController(ofType: PlacePageButtonsViewController.self) vc.buttonsData = placePageData.buttonsData! @@ -65,7 +65,7 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout { vc.delegate = interactor return vc } () - + lazy var actionBarViewController: ActionBarViewController = { let vc = storyboard.instantiateViewController(ofType: ActionBarViewController.self) vc.placePageData = placePageData @@ -82,13 +82,13 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout { lazy var placePageNavigationViewController: PlacePageHeaderViewController = { return PlacePageHeaderBuilder.build(data: placePageData.previewData, delegate: interactor, headerType: .fixed) } () - + init(interactor: PlacePageInteractor, storyboard: UIStoryboard, data: PlacePageData) { self.interactor = interactor self.storyboard = storyboard self.placePageData = data } - + private func configureViewControllers() -> [UIViewController] { var viewControllers = [UIViewController]() viewControllers.append(previewViewController) @@ -113,7 +113,7 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout { if placePageData.buttonsData != nil { viewControllers.append(buttonsViewController) } - + placePageData.onBookmarkStatusUpdate = { [weak self] in guard let self = self else { return } if self.placePageData.bookmarkData == nil { @@ -212,9 +212,8 @@ extension PlacePageCommonLayout: MWMLocationObserver { let altString = "▲ \(unitsFormatter.string(from: altMeasurement))" if location.speed > 0 && location.timestamp.timeIntervalSinceNow >= -2 { - let speed = imperial ? location.speed * 2.237 : location.speed * 3.6 - let speedMeasurement = Measurement(value: speed.rounded(), unit: imperial ? UnitSpeed.milesPerHour: UnitSpeed.kilometersPerHour) - let speedString = "\(LocationManager.speedSymbolFor(location.speed))\(unitsFormatter.string(from: speedMeasurement))" + let speedMeasure = Measure.init(asSpeed: location.speed) + let speedString = "\(LocationManager.speedSymbolFor(location.speed))\(speedMeasure.valueAsString) \(speedMeasure.unit)" previewViewController.updateSpeedAndAltitude("\(altString) \(speedString)") } else { previewViewController.updateSpeedAndAltitude(altString) diff --git a/iphone/Maps/UI/Storyboard/CarPlayStoryboard.storyboard b/iphone/Maps/UI/Storyboard/CarPlayStoryboard.storyboard index d4d1cefb39be5..5a1cda3e6b2c7 100644 --- a/iphone/Maps/UI/Storyboard/CarPlayStoryboard.storyboard +++ b/iphone/Maps/UI/Storyboard/CarPlayStoryboard.storyboard @@ -53,11 +53,11 @@ - +