diff --git a/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.h b/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.h index 31692b474..033d1d96a 100644 --- a/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.h +++ b/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.h @@ -1,8 +1,10 @@ #import "MWMTextToSpeechObserver.h" +#import @interface MWMTextToSpeech : NSObject + (MWMTextToSpeech *)tts; +- (AVSpeechSynthesisVoice *)voice; + (BOOL)isTTSEnabled; + (void)setTTSEnabled:(BOOL)enabled; + (BOOL)isStreetNamesTTSEnabled; diff --git a/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.mm b/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.mm index d82631f96..9169900f5 100644 --- a/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.mm +++ b/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.mm @@ -134,7 +134,10 @@ using Observers = NSHashTable; [ud setObject:locale forKey:kUserDefaultsTTSLanguageBcp47]; [self createVoice:locale]; } - +- (AVSpeechSynthesisVoice *)voice { + [self createVoice:[[self class] savedLanguage]]; + return self.speechVoice; +} - (BOOL)isValid { return _speechSynthesizer != nil && _speechVoice != nil; } + (BOOL)isTTSEnabled { return [NSUserDefaults.standardUserDefaults boolForKey:kIsTTSEnabled]; } + (void)setTTSEnabled:(BOOL)enabled { diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings index e022d3628..44efcdb01 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings @@ -539,6 +539,12 @@ "editor_report_problem_send_button" = "Send"; "autodownload" = "Auto-download maps"; +/* Voice */ +"voice" = "Voice"; +"voice_explanation" = "It's possible to pick a better voice in the system settings under *Accesibility*, *Read & Speak*, *Voices*."; +"voice_explanation_before_version26" = "It's possible to pick a better voice in the system settings under *Accesibility*, *Spoken Content*, *Voices*."; +"unknown" = "Unknown"; + /* Place page confirmation messages and time ago formatting */ "existence_confirmed_time_ago" = "Existence confirmed %@"; "hours_confirmed_time_ago" = "Confirmed %@"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings index 59f6174d9..b007593b5 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings @@ -558,7 +558,11 @@ "editor_report_problem_send_button" = "Send"; "autodownload" = "Auto-download maps"; - +/* Voice */ +"voice" = "Voice"; +"voice_explanation" = "It's possible to pick a better voice in the system settings under *Accesibility*, *Read & Speak*, *Voices*."; +"voice_explanation_before_version26" = "It's possible to pick a better voice in the system settings under *Accesibility*, *Spoken Content*, *Voices*."; +"unknown" = "Unknown"; /* Place page confirmation messages and time ago formatting */ "existence_confirmed_time_ago" = "Existence confirmed %@"; diff --git a/iphone/Maps/Model/Settings.swift b/iphone/Maps/Model/Settings.swift index 38bf484f8..df3ebdb31 100644 --- a/iphone/Maps/Model/Settings.swift +++ b/iphone/Maps/Model/Settings.swift @@ -1,4 +1,5 @@ import Combine +import AVFoundation /// The settings @objc class Settings: NSObject { @@ -325,6 +326,16 @@ import Combine } + /// The voice used for voice guidance during routing + @objc static var voiceForVoiceRouting: String? { + if let voice = MWMTextToSpeech.tts().voice() { + return voice.name + } + + return nil + } + + /// If street names should be announced in the voice guidance during routing @objc static var shouldAnnounceStreetnamesWhileVoiceRouting: Bool { get { diff --git a/iphone/Maps/UI/Settings/SettingsNavigationView.swift b/iphone/Maps/UI/Settings/SettingsNavigationView.swift index b1c420301..e88a57c13 100644 --- a/iphone/Maps/UI/Settings/SettingsNavigationView.swift +++ b/iphone/Maps/UI/Settings/SettingsNavigationView.swift @@ -4,6 +4,10 @@ import SwiftUI struct SettingsNavigationView: View { // MARK: Properties + /// The scene phase of the environment + @Environment(\.scenePhase) private var scenePhase + + /// If the perspective view should be used during routing @State var hasPerspectiveViewWhileRouting: Bool = true @@ -48,6 +52,10 @@ struct SettingsNavigationView: View { @State var shouldAvoidStepsWhileRouting: Bool = false + /// A date for forcing a refresh of the view + @State var forceRefreshDate: Date = Date.now + + /// The actual view var body: some View { List { @@ -73,6 +81,28 @@ struct SettingsNavigationView: View { Text("pref_tts_language_title") } + HStack { + VStack(alignment: .leading) { + Text("voice") + + if #available(iOS 26, *) { + Text("voice_explanation") + .font(.footnote) + .foregroundStyle(.secondary) + } else { + Text("voice_explanation_before_version26") + .font(.footnote) + .foregroundStyle(.secondary) + } + } + + Spacer() + + Text(Settings.voiceForVoiceRouting ?? "unknown") + .foregroundStyle(.secondary) + .id(UUID()) + } + Toggle(isOn: $shouldAnnounceStreetnamesWhileVoiceRouting) { VStack(alignment: .leading) { Text("pref_tts_street_names_title") @@ -111,6 +141,7 @@ struct SettingsNavigationView: View { .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0)) } } + .id(forceRefreshDate) Section { Toggle("avoid_tolls", isOn: $shouldAvoidTollRoadsWhileRouting) @@ -147,6 +178,9 @@ struct SettingsNavigationView: View { shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting } + .onChange(of: scenePhase) { _ in + forceRefreshDate = Date.now + } .onChange(of: hasPerspectiveViewWhileRouting) { changedHasPerspectiveViewWhileRouting in Settings.hasPerspectiveViewWhileRouting = changedHasPerspectiveViewWhileRouting }