From 8b4eab3444b5249677498f03194036a3bcbfccdb Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Sat, 27 Sep 2025 14:45:53 +0200 Subject: [PATCH] [ios] Add UI for avoiding paved roads Signed-off-by: Yannik Bloscheck --- .../SettingsTemplateBuilder.swift | 29 ++++- iphone/Maps/Core/Routing/MWMRouter.h | 3 +- iphone/Maps/Core/Routing/MWMRouter.mm | 3 + iphone/Maps/Core/Settings/MWMRoutingOptions.h | 1 + .../Maps/Core/Settings/MWMRoutingOptions.mm | 13 +- .../Contents.json | 12 ++ .../options.paved.slash.svg | 122 ++++++++++++++++++ .../options.paved.symbolset/Contents.json | 12 ++ .../options.paved.symbolset/options.paved.svg | 109 ++++++++++++++++ .../options.unpaved.slash.svg | 69 +++++----- .../options.unpaved.svg | 46 +++---- .../de.lproj/Localizable.strings | 1 + .../en-GB.lproj/Localizable.strings | 1 + .../en.lproj/Localizable.strings | 1 + iphone/Maps/Model/Settings.swift | 15 +++ .../Maps/UI/Routing/RoutingOptionsView.swift | 20 ++- .../UI/Settings/SettingsNavigationView.swift | 20 ++- 17 files changed, 413 insertions(+), 64 deletions(-) create mode 100644 iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.slash.symbolset/Contents.json create mode 100644 iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.slash.symbolset/options.paved.slash.svg create mode 100644 iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.symbolset/Contents.json create mode 100644 iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.symbolset/options.paved.svg diff --git a/iphone/Maps/Classes/CarPlay/Template Builders/SettingsTemplateBuilder.swift b/iphone/Maps/Classes/CarPlay/Template Builders/SettingsTemplateBuilder.swift index 6b6f73589..d53c10a9e 100644 --- a/iphone/Maps/Classes/CarPlay/Template Builders/SettingsTemplateBuilder.swift +++ b/iphone/Maps/Classes/CarPlay/Template Builders/SettingsTemplateBuilder.swift @@ -15,6 +15,7 @@ final class SettingsTemplateBuilder { return [createUnpavedButton(options: options), createTollButton(options: options), createFerryButton(options: options), + createPavedButton(options: options), createStepsButton(options: options), createSpeedcamButton()] } @@ -40,7 +41,7 @@ final class SettingsTemplateBuilder { private class func createUnpavedButton(options: RoutingOptions) -> CPGridButton { var unpavedIconName = "options.unpaved" - if options.avoidDirty { unpavedIconName += ".slash" } + if options.avoidDirty && !options.avoidPaved { unpavedIconName += ".slash" } let configuration = UIImage.SymbolConfiguration(textStyle: .title1) var image = UIImage(named: unpavedIconName, in: nil, with: configuration)! if #unavailable(iOS 26) { @@ -49,12 +50,38 @@ final class SettingsTemplateBuilder { } let unpavedButton = CPGridButton(titleVariants: [L("avoid_unpaved")], image: image) { _ in options.avoidDirty = !options.avoidDirty + if options.avoidDirty { + options.avoidPaved = false + } options.save() CarPlayService.shared.updateRouteAfterChangingSettings() CarPlayService.shared.popTemplate(animated: true) } + unpavedButton.isEnabled = !options.avoidPaved return unpavedButton } + + private class func createPavedButton(options: RoutingOptions) -> CPGridButton { + var pavedIconName = "options.paved" + if options.avoidPaved && !options.avoidDirty { pavedIconName += ".slash" } + let configuration = UIImage.SymbolConfiguration(textStyle: .title1) + var image = UIImage(named: pavedIconName, in: nil, with: configuration)! + if #unavailable(iOS 26) { + image = image.withTintColor(.white, renderingMode: .alwaysTemplate) + image = UIImage(data: image.pngData()!)!.withRenderingMode(.alwaysTemplate) + } + let pavedButton = CPGridButton(titleVariants: [L("avoid_paved")], image: image) { _ in + options.avoidPaved = !options.avoidPaved + if options.avoidPaved { + options.avoidDirty = false + } + options.save() + CarPlayService.shared.updateRouteAfterChangingSettings() + CarPlayService.shared.popTemplate(animated: true) + } + pavedButton.isEnabled = !options.avoidDirty + return pavedButton + } private class func createFerryButton(options: RoutingOptions) -> CPGridButton { var ferryIconName = "options.ferries" diff --git a/iphone/Maps/Core/Routing/MWMRouter.h b/iphone/Maps/Core/Routing/MWMRouter.h index 86d8f5798..2901437b6 100644 --- a/iphone/Maps/Core/Routing/MWMRouter.h +++ b/iphone/Maps/Core/Routing/MWMRouter.h @@ -6,7 +6,8 @@ typedef NS_ENUM(NSInteger, MWMRoadType) { MWMRoadTypeDirty, MWMRoadTypeFerry, MWMRoadTypeMotorway, - MWMRoadTypeSteps + MWMRoadTypeSteps, + MWMRoadTypePaved }; typedef void (^MWMImageHeightBlock)(UIImage *, NSString *, NSString *); diff --git a/iphone/Maps/Core/Routing/MWMRouter.mm b/iphone/Maps/Core/Routing/MWMRouter.mm index 9d2bdaceb..82385be43 100644 --- a/iphone/Maps/Core/Routing/MWMRouter.mm +++ b/iphone/Maps/Core/Routing/MWMRouter.mm @@ -592,6 +592,9 @@ char const *kRenderAltitudeImagesQueueLabel = "mapsme.mwmrouter.renderAltitudeIm case MWMRoadTypeDirty: options.avoidDirty = YES; break; + case MWMRoadTypePaved: + options.avoidPaved = YES; + break; case MWMRoadTypeFerry: options.avoidFerry = YES; break; diff --git a/iphone/Maps/Core/Settings/MWMRoutingOptions.h b/iphone/Maps/Core/Settings/MWMRoutingOptions.h index b45daf049..6d96e973d 100644 --- a/iphone/Maps/Core/Settings/MWMRoutingOptions.h +++ b/iphone/Maps/Core/Settings/MWMRoutingOptions.h @@ -7,6 +7,7 @@ NS_SWIFT_NAME(RoutingOptions) @property(nonatomic) BOOL avoidToll; @property(nonatomic) BOOL avoidDirty; +@property(nonatomic) BOOL avoidPaved; @property(nonatomic) BOOL avoidFerry; @property(nonatomic) BOOL avoidMotorway; @property(nonatomic) BOOL avoidSteps; diff --git a/iphone/Maps/Core/Settings/MWMRoutingOptions.mm b/iphone/Maps/Core/Settings/MWMRoutingOptions.mm index c05892a7f..d6b80a8c8 100644 --- a/iphone/Maps/Core/Settings/MWMRoutingOptions.mm +++ b/iphone/Maps/Core/Settings/MWMRoutingOptions.mm @@ -35,6 +35,14 @@ [self setOption:(routing::RoutingOptions::Road::Dirty) enabled:avoid]; } +- (BOOL)avoidPaved { + return _options.Has(routing::RoutingOptions::Road::Paved); +} + +- (void)setAvoidPaved:(BOOL)avoid { + [self setOption:(routing::RoutingOptions::Road::Paved) enabled:avoid]; +} + - (BOOL)avoidFerry { return _options.Has(routing::RoutingOptions::Road::Ferry); } @@ -60,7 +68,7 @@ } - (BOOL)hasOptions { - return self.avoidToll || self.avoidDirty || self.avoidFerry || self.avoidMotorway || self.avoidSteps; + return self.avoidToll || self.avoidDirty || self.avoidPaved|| self.avoidFerry || self.avoidMotorway || self.avoidSteps; } - (void)save { @@ -80,8 +88,7 @@ return NO; } MWMRoutingOptions *another = (MWMRoutingOptions *)object; - return another.avoidToll == self.avoidToll && another.avoidDirty == self.avoidDirty && - another.avoidFerry == self.avoidFerry && another.avoidMotorway == self.avoidMotorway && another.avoidSteps == self.avoidSteps; + return another.avoidToll == self.avoidToll && another.avoidDirty == self.avoidDirty && another.avoidPaved == self.avoidPaved && another.avoidFerry == self.avoidFerry && another.avoidMotorway == self.avoidMotorway && another.avoidSteps == self.avoidSteps; } @end diff --git a/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.slash.symbolset/Contents.json b/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.slash.symbolset/Contents.json new file mode 100644 index 000000000..cee240622 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.slash.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "options.paved.slash.svg", + "idiom" : "universal" + } + ] +} diff --git a/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.slash.symbolset/options.paved.slash.svg b/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.slash.symbolset/options.paved.slash.svg new file mode 100644 index 000000000..627459117 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.slash.symbolset/options.paved.slash.svg @@ -0,0 +1,122 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.4.0 + Requires Xcode 14 or greater + Generated from + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.symbolset/Contents.json b/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.symbolset/Contents.json new file mode 100644 index 000000000..7f607e64f --- /dev/null +++ b/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "options.paved.svg", + "idiom" : "universal" + } + ] +} diff --git a/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.symbolset/options.paved.svg b/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.symbolset/options.paved.svg new file mode 100644 index 000000000..175f01cc9 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Interface/Symbols/options.paved.symbolset/options.paved.svg @@ -0,0 +1,109 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.4.0 + Requires Xcode 14 or greater + Generated from circle + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/Images.xcassets/Interface/Symbols/options.unpaved.slash.symbolset/options.unpaved.slash.svg b/iphone/Maps/Images.xcassets/Interface/Symbols/options.unpaved.slash.symbolset/options.unpaved.slash.svg index 05766c3ca..c9c21e6e1 100644 --- a/iphone/Maps/Images.xcassets/Interface/Symbols/options.unpaved.slash.symbolset/options.unpaved.slash.svg +++ b/iphone/Maps/Images.xcassets/Interface/Symbols/options.unpaved.slash.symbolset/options.unpaved.slash.svg @@ -4,23 +4,26 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> - - diff --git a/iphone/Maps/Images.xcassets/Interface/Symbols/options.unpaved.symbolset/options.unpaved.svg b/iphone/Maps/Images.xcassets/Interface/Symbols/options.unpaved.symbolset/options.unpaved.svg index 1bd826730..58db41dfb 100644 --- a/iphone/Maps/Images.xcassets/Interface/Symbols/options.unpaved.symbolset/options.unpaved.svg +++ b/iphone/Maps/Images.xcassets/Interface/Symbols/options.unpaved.symbolset/options.unpaved.svg @@ -4,17 +4,19 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> - - diff --git a/iphone/Maps/LocalizedStrings/de.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/de.lproj/Localizable.strings index 04a49aaf4..fcf4a6c5d 100644 --- a/iphone/Maps/LocalizedStrings/de.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/de.lproj/Localizable.strings @@ -823,6 +823,7 @@ /* Recommended length for CarPlay and Android Auto is around 25-27 characters */ "avoid_unpaved" = "Unbefest. Straßen vermeiden"; +"avoid_paved" = "Befst. Straßen vermeiden"; /* Recommended length for CarPlay and Android Auto is around 25-27 characters */ "avoid_steps" = "Treppen vermeiden"; diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings index 44efcdb01..0bba18f8f 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings @@ -850,6 +850,7 @@ /* Recommended length for CarPlay and Android Auto is around 25-27 characters */ "avoid_unpaved" = "Avoid unpaved roads"; +"avoid_paved" = "Avoid paved roads"; /* Recommended length for CarPlay and Android Auto is around 25-27 characters */ "avoid_steps" = "Avoid stairs"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings index b007593b5..426e9dfef 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings @@ -871,6 +871,7 @@ /* Recommended length for CarPlay and Android Auto is around 25-27 characters */ "avoid_unpaved" = "Avoid unpaved roads"; +"avoid_paved" = "Avoid paved roads"; /* Recommended length for CarPlay and Android Auto is around 25-27 characters */ "avoid_steps" = "Avoid stairs"; diff --git a/iphone/Maps/Model/Settings.swift b/iphone/Maps/Model/Settings.swift index df3ebdb31..c5de941ac 100644 --- a/iphone/Maps/Model/Settings.swift +++ b/iphone/Maps/Model/Settings.swift @@ -388,6 +388,21 @@ import AVFoundation } + /// If paved roads should be avoided during routing + @objc static var shouldAvoidPavedRoadsWhileRouting: Bool { + get { + return RoutingOptions().avoidPaved + } + set { + let routingOptions = RoutingOptions() + routingOptions.avoidPaved = newValue + routingOptions.save() + + NotificationCenter.default.post(name: routingOptionsChangedNotificationName, object: nil) + } + } + + /// If ferries should be avoided during routing @objc static var shouldAvoidFerriesWhileRouting: Bool { get { diff --git a/iphone/Maps/UI/Routing/RoutingOptionsView.swift b/iphone/Maps/UI/Routing/RoutingOptionsView.swift index 8559e8010..2270f3a93 100644 --- a/iphone/Maps/UI/Routing/RoutingOptionsView.swift +++ b/iphone/Maps/UI/Routing/RoutingOptionsView.swift @@ -16,6 +16,10 @@ struct RoutingOptionsView: View { @State var shouldAvoidUnpavedRoadsWhileRouting: Bool = false + /// If paved roads should be avoided during routing + @State var shouldAvoidPavedRoadsWhileRouting: Bool = false + + /// If ferries should be avoided during routing @State var shouldAvoidFerriesWhileRouting: Bool = false @@ -38,6 +42,7 @@ struct RoutingOptionsView: View { Toggle("avoid_unpaved", isOn: $shouldAvoidUnpavedRoadsWhileRouting) .tint(.accent) + .disabled(shouldAvoidPavedRoadsWhileRouting) Toggle("avoid_ferry", isOn: $shouldAvoidFerriesWhileRouting) .tint(.accent) @@ -47,6 +52,10 @@ struct RoutingOptionsView: View { Toggle("avoid_steps", isOn: $shouldAvoidStepsWhileRouting) .tint(.accent) + + Toggle("avoid_paved", isOn: $shouldAvoidPavedRoadsWhileRouting) + .tint(.accent) + .disabled(shouldAvoidUnpavedRoadsWhileRouting) } } .navigationTitle(String(localized: "driving_options_title")) @@ -65,6 +74,7 @@ struct RoutingOptionsView: View { .onAppear { shouldAvoidTollRoadsWhileRouting = Settings.shouldAvoidTollRoadsWhileRouting shouldAvoidUnpavedRoadsWhileRouting = Settings.shouldAvoidUnpavedRoadsWhileRouting + shouldAvoidPavedRoadsWhileRouting = Settings.shouldAvoidPavedRoadsWhileRouting shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting @@ -74,9 +84,15 @@ struct RoutingOptionsView: View { } .onChange(of: shouldAvoidUnpavedRoadsWhileRouting) { changedShouldAvoidUnpavedRoadsWhileRouting in Settings.shouldAvoidUnpavedRoadsWhileRouting = changedShouldAvoidUnpavedRoadsWhileRouting + if changedShouldAvoidUnpavedRoadsWhileRouting { + shouldAvoidPavedRoadsWhileRouting = false + } } - .onChange(of: shouldAvoidUnpavedRoadsWhileRouting) { changedShouldAvoidUnpavedRoadsWhileRouting in - Settings.shouldAvoidUnpavedRoadsWhileRouting = changedShouldAvoidUnpavedRoadsWhileRouting + .onChange(of: shouldAvoidPavedRoadsWhileRouting) { changedShouldAvoidPavedRoadsWhileRouting in + Settings.shouldAvoidPavedRoadsWhileRouting = changedShouldAvoidPavedRoadsWhileRouting + if changedShouldAvoidPavedRoadsWhileRouting { + shouldAvoidUnpavedRoadsWhileRouting = false + } } .onChange(of: shouldAvoidFerriesWhileRouting) { changedShouldAvoidFerriesWhileRouting in Settings.shouldAvoidFerriesWhileRouting = changedShouldAvoidFerriesWhileRouting diff --git a/iphone/Maps/UI/Settings/SettingsNavigationView.swift b/iphone/Maps/UI/Settings/SettingsNavigationView.swift index e88a57c13..98fe54f18 100644 --- a/iphone/Maps/UI/Settings/SettingsNavigationView.swift +++ b/iphone/Maps/UI/Settings/SettingsNavigationView.swift @@ -40,6 +40,10 @@ struct SettingsNavigationView: View { @State var shouldAvoidUnpavedRoadsWhileRouting: Bool = false + /// If paved roads should be avoided during routing + @State var shouldAvoidPavedRoadsWhileRouting: Bool = false + + /// If ferries should be avoided during routing @State var shouldAvoidFerriesWhileRouting: Bool = false @@ -149,6 +153,7 @@ struct SettingsNavigationView: View { Toggle("avoid_unpaved", isOn: $shouldAvoidUnpavedRoadsWhileRouting) .tint(.accent) + .disabled(shouldAvoidPavedRoadsWhileRouting) Toggle("avoid_ferry", isOn: $shouldAvoidFerriesWhileRouting) .tint(.accent) @@ -158,6 +163,10 @@ struct SettingsNavigationView: View { Toggle("avoid_steps", isOn: $shouldAvoidStepsWhileRouting) .tint(.accent) + + Toggle("avoid_paved", isOn: $shouldAvoidPavedRoadsWhileRouting) + .tint(.accent) + .disabled(shouldAvoidUnpavedRoadsWhileRouting) } header: { Text("driving_options_title") } @@ -174,6 +183,7 @@ struct SettingsNavigationView: View { selectedAnnouncingSpeedTrapsWhileVoiceRouting = Settings.announcingSpeedTrapsWhileVoiceRouting shouldAvoidTollRoadsWhileRouting = Settings.shouldAvoidTollRoadsWhileRouting shouldAvoidUnpavedRoadsWhileRouting = Settings.shouldAvoidUnpavedRoadsWhileRouting + shouldAvoidPavedRoadsWhileRouting = Settings.shouldAvoidPavedRoadsWhileRouting shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting @@ -206,9 +216,15 @@ struct SettingsNavigationView: View { } .onChange(of: shouldAvoidUnpavedRoadsWhileRouting) { changedShouldAvoidUnpavedRoadsWhileRouting in Settings.shouldAvoidUnpavedRoadsWhileRouting = changedShouldAvoidUnpavedRoadsWhileRouting + if changedShouldAvoidUnpavedRoadsWhileRouting { + shouldAvoidPavedRoadsWhileRouting = false + } } - .onChange(of: shouldAvoidUnpavedRoadsWhileRouting) { changedShouldAvoidUnpavedRoadsWhileRouting in - Settings.shouldAvoidUnpavedRoadsWhileRouting = changedShouldAvoidUnpavedRoadsWhileRouting + .onChange(of: shouldAvoidPavedRoadsWhileRouting) { changedShouldAvoidPavedRoadsWhileRouting in + Settings.shouldAvoidPavedRoadsWhileRouting = changedShouldAvoidPavedRoadsWhileRouting + if changedShouldAvoidPavedRoadsWhileRouting { + shouldAvoidUnpavedRoadsWhileRouting = false + } } .onChange(of: shouldAvoidFerriesWhileRouting) { changedShouldAvoidFerriesWhileRouting in Settings.shouldAvoidFerriesWhileRouting = changedShouldAvoidFerriesWhileRouting