[ios] Display navigation voice and change instructions in settings

Signed-off-by: Yannik Bloscheck <git@yannikbloscheck.com>
This commit is contained in:
Yannik Bloscheck
2025-10-04 17:04:38 +02:00
committed by Yannik Bloscheck
parent 96d51dfcf9
commit 053f35b519
6 changed files with 62 additions and 2 deletions

View File

@@ -1,8 +1,10 @@
#import "MWMTextToSpeechObserver.h" #import "MWMTextToSpeechObserver.h"
#import <AVFoundation/AVFoundation.h>
@interface MWMTextToSpeech : NSObject @interface MWMTextToSpeech : NSObject
+ (MWMTextToSpeech *)tts; + (MWMTextToSpeech *)tts;
- (AVSpeechSynthesisVoice *)voice;
+ (BOOL)isTTSEnabled; + (BOOL)isTTSEnabled;
+ (void)setTTSEnabled:(BOOL)enabled; + (void)setTTSEnabled:(BOOL)enabled;
+ (BOOL)isStreetNamesTTSEnabled; + (BOOL)isStreetNamesTTSEnabled;

View File

@@ -134,7 +134,10 @@ using Observers = NSHashTable<Observer>;
[ud setObject:locale forKey:kUserDefaultsTTSLanguageBcp47]; [ud setObject:locale forKey:kUserDefaultsTTSLanguageBcp47];
[self createVoice:locale]; [self createVoice:locale];
} }
- (AVSpeechSynthesisVoice *)voice {
[self createVoice:[[self class] savedLanguage]];
return self.speechVoice;
}
- (BOOL)isValid { return _speechSynthesizer != nil && _speechVoice != nil; } - (BOOL)isValid { return _speechSynthesizer != nil && _speechVoice != nil; }
+ (BOOL)isTTSEnabled { return [NSUserDefaults.standardUserDefaults boolForKey:kIsTTSEnabled]; } + (BOOL)isTTSEnabled { return [NSUserDefaults.standardUserDefaults boolForKey:kIsTTSEnabled]; }
+ (void)setTTSEnabled:(BOOL)enabled { + (void)setTTSEnabled:(BOOL)enabled {

View File

@@ -539,6 +539,12 @@
"editor_report_problem_send_button" = "Send"; "editor_report_problem_send_button" = "Send";
"autodownload" = "Auto-download maps"; "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 */ /* Place page confirmation messages and time ago formatting */
"existence_confirmed_time_ago" = "Existence confirmed %@"; "existence_confirmed_time_ago" = "Existence confirmed %@";
"hours_confirmed_time_ago" = "Confirmed %@"; "hours_confirmed_time_ago" = "Confirmed %@";

View File

@@ -558,7 +558,11 @@
"editor_report_problem_send_button" = "Send"; "editor_report_problem_send_button" = "Send";
"autodownload" = "Auto-download maps"; "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 */ /* Place page confirmation messages and time ago formatting */
"existence_confirmed_time_ago" = "Existence confirmed %@"; "existence_confirmed_time_ago" = "Existence confirmed %@";

View File

@@ -1,4 +1,5 @@
import Combine import Combine
import AVFoundation
/// The settings /// The settings
@objc class Settings: NSObject { @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 /// If street names should be announced in the voice guidance during routing
@objc static var shouldAnnounceStreetnamesWhileVoiceRouting: Bool { @objc static var shouldAnnounceStreetnamesWhileVoiceRouting: Bool {
get { get {

View File

@@ -4,6 +4,10 @@ import SwiftUI
struct SettingsNavigationView: View { struct SettingsNavigationView: View {
// MARK: Properties // MARK: Properties
/// The scene phase of the environment
@Environment(\.scenePhase) private var scenePhase
/// If the perspective view should be used during routing /// If the perspective view should be used during routing
@State var hasPerspectiveViewWhileRouting: Bool = true @State var hasPerspectiveViewWhileRouting: Bool = true
@@ -48,6 +52,10 @@ struct SettingsNavigationView: View {
@State var shouldAvoidStepsWhileRouting: Bool = false @State var shouldAvoidStepsWhileRouting: Bool = false
/// A date for forcing a refresh of the view
@State var forceRefreshDate: Date = Date.now
/// The actual view /// The actual view
var body: some View { var body: some View {
List { List {
@@ -73,6 +81,28 @@ struct SettingsNavigationView: View {
Text("pref_tts_language_title") 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) { Toggle(isOn: $shouldAnnounceStreetnamesWhileVoiceRouting) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("pref_tts_street_names_title") Text("pref_tts_street_names_title")
@@ -111,6 +141,7 @@ struct SettingsNavigationView: View {
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0)) .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
} }
} }
.id(forceRefreshDate)
Section { Section {
Toggle("avoid_tolls", isOn: $shouldAvoidTollRoadsWhileRouting) Toggle("avoid_tolls", isOn: $shouldAvoidTollRoadsWhileRouting)
@@ -147,6 +178,9 @@ struct SettingsNavigationView: View {
shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting
shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting
} }
.onChange(of: scenePhase) { _ in
forceRefreshDate = Date.now
}
.onChange(of: hasPerspectiveViewWhileRouting) { changedHasPerspectiveViewWhileRouting in .onChange(of: hasPerspectiveViewWhileRouting) { changedHasPerspectiveViewWhileRouting in
Settings.hasPerspectiveViewWhileRouting = changedHasPerspectiveViewWhileRouting Settings.hasPerspectiveViewWhileRouting = changedHasPerspectiveViewWhileRouting
} }