diff --git a/iphone/Maps/Classes/Common.h b/iphone/Maps/Classes/Common.h index da29738e69..1af14cdf67 100644 --- a/iphone/Maps/Classes/Common.h +++ b/iphone/Maps/Classes/Common.h @@ -72,24 +72,6 @@ static inline NSString * formattedSize(uint64_t size) return [sizeString uppercaseString]; } -static inline NSString * bcp47ToTwineLanguage(NSString const * bcp47LangName) -{ - if (bcp47LangName == nil || [bcp47LangName length] < 2) - return nil; - - if ([bcp47LangName isEqualToString:@"zh-CN"] || [bcp47LangName isEqualToString:@"zh-CHS"] - || [bcp47LangName isEqualToString:@"zh-SG"]) - { - return @"zh-Hans"; // Chinese simplified - } - - if ([bcp47LangName hasPrefix:@"zh"]) - return @"zh-Hant"; // Chinese traditional - - // Taking two first symbols of a language name. For example ru-RU -> ru - return [bcp47LangName substringToIndex:2]; -} - // Use only for screen dimensions CGFloat comparison static inline BOOL equalScreenDimensions(CGFloat left, CGFloat right) { diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h index e42b88fd06..ef500fbcd8 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h @@ -37,7 +37,6 @@ #pragma mark - MWMNavigationDashboardManager - (void)setupRoutingDashboard:(location::FollowingInfo const &)info; -- (void)playTurnNotifications; - (void)routingHidden; - (void)routingReady; - (void)routingPrepare; diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm index e41aede8af..49c1b7e8d7 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm @@ -322,11 +322,6 @@ extern NSString * const kAlohalyticsTapEventKey; [self.menuController setStreetName:@(info.m_sourceName.c_str())]; } -- (void)playTurnNotifications -{ - [self.navigationManager playTurnNotifications]; -} - - (void)handleRoutingError { self.navigationManager.state = MWMNavigationDashboardStateError; diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.mm index f2ccba4de9..6ca8d33254 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.mm +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardEntity.mm @@ -51,8 +51,13 @@ using namespace routing::turns; // _lanes = info.m_lanes; _nextTurnImage = image(info.m_nextTurn, true); } - _turnImage = image(info.m_turn, false); - if (info.m_turn == TurnDirection::EnterRoundAbout || info.m_turn == TurnDirection::LeaveRoundAbout) + + TurnDirection const turn = info.m_turn; + _turnImage = image(turn, false); + BOOL const isRound = turn == TurnDirection::EnterRoundAbout || + turn == TurnDirection::StayOnRoundAbout || + turn == TurnDirection::LeaveRoundAbout; + if (isRound) _roundExitNumber = info.m_exitNum; else _roundExitNumber = 0; diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.h b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.h index c899cec6bf..3a7fac40f7 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.h +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.h @@ -43,7 +43,6 @@ typedef NS_ENUM(NSUInteger, MWMNavigationDashboardState) - (instancetype)initWithParentView:(UIView *)view delegate:(id)delegate; - (void)setupDashboard:(location::FollowingInfo const &)info; - (void)updateDashboard; -- (void)playTurnNotifications; - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation; - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator; diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.mm index 1bcf69f72e..5379048afc 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.mm +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.mm @@ -32,7 +32,6 @@ static NSString * const kNavigationDashboardIPADXibName = @"MWMNiPadNavigationDa @property (weak, nonatomic) UIView * ownerView; @property (nonatomic) MWMNavigationDashboardEntity * entity; -@property (nonatomic) MWMTextToSpeech * tts; //@property (nonatomic) MWMLanesPanel * lanesPanel; @property (nonatomic) MWMNextTurnPanel * nextTurnPanel; @property (nonatomic) MWMRouteHelperPanelsDrawer * drawer; @@ -78,12 +77,26 @@ static NSString * const kNavigationDashboardIPADXibName = @"MWMNiPadNavigationDa _navigationDashboard = isPortrait ? _navigationDashboardPortrait : _navigationDashboardLandscape; _navigationDashboardPortrait.delegate = _navigationDashboardLandscape.delegate = delegate; } - _tts = [[MWMTextToSpeech alloc] init]; _helperPanels = [NSMutableArray array]; } return self; } +- (void)changedTTSStatus:(NSNotification *)notification +{ + if (self.state != MWMNavigationDashboardStateNavigation) + return; + NSDictionary * userInfo = notification.userInfo; + BOOL const enabled = userInfo[@"on"].boolValue; + self.navigationDashboardPortrait.soundButton.selected = enabled; + self.navigationDashboardLandscape.soundButton.selected = enabled; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + #pragma mark - Layout - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation @@ -142,12 +155,6 @@ static NSString * const kNavigationDashboardIPADXibName = @"MWMNiPadNavigationDa [self updateDashboard]; } -- (void)playTurnNotifications -{ - if (self.state == MWMNavigationDashboardStateNavigation) - [self.tts playTurnNotifications]; -} - - (void)handleError { [self.routePreview stateError]; @@ -192,10 +199,7 @@ static NSString * const kNavigationDashboardIPADXibName = @"MWMNiPadNavigationDa return; case 1: if (![self.helperPanels.firstObject isKindOfClass:panel.class]) - { [self.helperPanels addObject:panel]; - return; - } return; case 2: for (MWMRouteHelperPanel * p in self.helperPanels) @@ -272,6 +276,17 @@ static NSString * const kNavigationDashboardIPADXibName = @"MWMNiPadNavigationDa [self.delegate didCancelRouting]; } +- (IBAction)soundTap:(UIButton *)sender +{ + BOOL const isEnable = !sender.selected; + MWMTextToSpeech * tts = [MWMTextToSpeech tts]; + if (isEnable) + [tts enable]; + else + [tts disable]; + sender.selected = isEnable; +} + #pragma mark - MWMNavigationGo - (IBAction)navigationGoPressed:(UIButton *)sender @@ -316,6 +331,13 @@ static NSString * const kNavigationDashboardIPADXibName = @"MWMNiPadNavigationDa - (void)showStateNavigation { [self.routePreview remove]; + MWMTextToSpeech * tts = [MWMTextToSpeech tts]; + BOOL const isNeedToEnable = tts.isNeedToEnable; + self.navigationDashboardPortrait.soundButton.selected = isNeedToEnable; + self.navigationDashboardLandscape.soundButton.selected = isNeedToEnable; + if (isNeedToEnable) { + [tts enable]; + } [self.navigationDashboard addToView:self.ownerView]; } diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.h b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.h index f73d9e1572..ca82cbc650 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.h +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.h @@ -1,14 +1,33 @@ -#import - #include "std/string.hpp" #include "std/vector.hpp" @interface MWMTextToSpeech : NSObject -- (instancetype)init; ++ (instancetype)tts; + +- (vector>)availableLanguages; +- (NSString *)savedLanguage; +- (void)setNotificationsLocale:(string const &)locale; +- (BOOL)isNeedToEnable; +- (void)setNeedToEnable:(BOOL)need; - (BOOL)isEnable; - (void)enable; - (void)disable; - (void)playTurnNotifications; +- (instancetype)init __attribute__((unavailable("call tts instead"))); +- (instancetype)copy __attribute__((unavailable("call tts instead"))); +- (instancetype)copyWithZone:(NSZone *)zone __attribute__((unavailable("call tts instead"))); ++ (instancetype)alloc __attribute__((unavailable("call tts instead"))); ++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable("call tts instead"))); ++ (instancetype)new __attribute__((unavailable("call tts instead"))); + @end + +namespace tts +{ + +string bcp47ToTwineLanguage(NSString const * bcp47LangName); +string translatedTwine(string const & twine); + +} // namespace tts diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm index 2e83c4a241..5183a7075c 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm @@ -1,21 +1,38 @@ -#import #import "Common.h" +#import #import "MWMTextToSpeech.h" #include "Framework.h" +#include "sound/tts/languages.hpp" -extern NSString * const kMwmTextToSpeechEnable = @"MWMTEXTTOSPEECH_ENABLE"; -extern NSString * const kMwmTextToSpeechDisable = @"MWMTEXTTOSPEECH_DISABLE"; +extern NSString * const kUserDefaultsTTSLanguage = @"UserDefaultsTTSLanguage"; +extern NSString * const kUserDafaultsNeedToEnableTTS = @"UserDefaultsNeedToEnableTTS"; @interface MWMTextToSpeech() +{ + vector> _availableLanguages; +} + @property (nonatomic) AVSpeechSynthesizer * speechSynthesizer; @property (nonatomic) AVSpeechSynthesisVoice * speechVoice; @property (nonatomic) float speechRate; + @end @implementation MWMTextToSpeech -- (instancetype)init ++ (instancetype)tts +{ + static dispatch_once_t onceToken; + static MWMTextToSpeech * tts = nil; + dispatch_once(&onceToken, ^ + { + tts = [[super alloc] initTTS]; + }); + return tts; +} + +- (instancetype)initTTS { self = [super init]; if (self) @@ -27,27 +44,40 @@ extern NSString * const kMwmTextToSpeechDisable = @"MWMTEXTTOSPEECH_DISABLE"; LOG(LWARNING, ("[ setCategory]] error.", [err localizedDescription])); if (![audioSession setActive:YES error:&err]) LOG(LWARNING, ("[[AVAudioSession sharedInstance] setActive]] error.", [err localizedDescription])); - + + _availableLanguages = availableLanguages(); + + NSString * saved = self.savedLanguage; + + string preferedLanguage ; + if (saved.length) + preferedLanguage = saved.UTF8String; + else + preferedLanguage = tts::bcp47ToTwineLanguage([AVSpeechSynthesisVoice currentLanguageCode]); + + pair const lan {preferedLanguage, tts::translatedTwine(preferedLanguage)}; + if (find(_availableLanguages.begin(), _availableLanguages.end(), lan) != _availableLanguages.end()) + [self setNotificationsLocale:preferedLanguage]; + else + [self setNotificationsLocale:"en"]; // Before 9.0 version iOS has an issue with speechRate. AVSpeechUtteranceDefaultSpeechRate does not work correctly. // It's a work around for iOS 7.x and 8.x. - self.speechRate = isIOSVersionLessThan(@"7.1.1") ? 0.3 : (isIOSVersionLessThan(@"9.0.0") ? 0.15 : AVSpeechUtteranceDefaultSpeechRate); - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(enable) - name:kMwmTextToSpeechEnable - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(disable) - name:kMwmTextToSpeechDisable - object:nil]; + _speechRate = isIOSVersionLessThan(@"7.1.1") ? 0.3 : (isIOSVersionLessThan(@"9.0.0") ? 0.15 : AVSpeechUtteranceDefaultSpeechRate); } return self; } --(void)dealloc +- (vector>)availableLanguages { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + return _availableLanguages; +} + +- (void)setNotificationsLocale:(string const &)locale +{ + NSUserDefaults * ud = [NSUserDefaults standardUserDefaults]; + [ud setObject:@(locale.c_str()) forKey:kUserDefaultsTTSLanguage]; + GetFramework().SetTurnNotificationsLocale(locale); + [ud synchronize]; } - (BOOL)isValid @@ -55,16 +85,32 @@ extern NSString * const kMwmTextToSpeechDisable = @"MWMTEXTTOSPEECH_DISABLE"; return _speechSynthesizer != nil && _speechVoice != nil; } +- (BOOL)isNeedToEnable +{ + return [[NSUserDefaults standardUserDefaults] boolForKey:kUserDafaultsNeedToEnableTTS]; +} + +- (void)setNeedToEnable:(BOOL)need +{ + NSUserDefaults * ud = [NSUserDefaults standardUserDefaults]; + [ud setBool:need forKey:kUserDafaultsNeedToEnableTTS]; + [ud synchronize]; +} + - (void)enable { - if (![self isValid]) - [self createSynthesizer]; - - GetFramework().EnableTurnNotifications(true); + [self setNeedToEnable:YES]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ + { + if (![self isValid]) + [self createSynthesizer]; + GetFramework().EnableTurnNotifications(true); + }); } - (void)disable { + [self setNeedToEnable:NO]; GetFramework().EnableTurnNotifications(false); } @@ -73,13 +119,17 @@ extern NSString * const kMwmTextToSpeechDisable = @"MWMTEXTTOSPEECH_DISABLE"; return GetFramework().AreTurnNotificationsEnabled() ? YES : NO; } +- (NSString *)savedLanguage +{ + return [[NSUserDefaults standardUserDefaults] stringForKey:kUserDefaultsTTSLanguage]; +} + - (void)createSynthesizer { self.speechSynthesizer = [[AVSpeechSynthesizer alloc] init]; - + [self createVoice:self.savedLanguage]; // TODO(vbykoianko) Use [NSLocale preferredLanguages] instead of [AVSpeechSynthesisVoice currentLanguageCode]. // [AVSpeechSynthesisVoice currentLanguageCode] is used now because of we need a language code in BCP-47. - [self createVoice:[AVSpeechSynthesisVoice currentLanguageCode]]; } - (void)createVoice:(NSString *)locale @@ -113,13 +163,13 @@ extern NSString * const kMwmTextToSpeechDisable = @"MWMTEXTTOSPEECH_DISABLE"; } self.speechVoice = [AVSpeechSynthesisVoice voiceWithLanguage:locale]; - NSString const * twineLang = bcp47ToTwineLanguage(locale); - if (twineLang == nil) + string const twineLang = tts::bcp47ToTwineLanguage(locale); + if (twineLang.empty()) { LOG(LERROR, ("Cannot convert UI locale or default locale to twine language. MWMTestToSpeech is invalid.")); return; // self is not valid. } - GetFramework().SetTurnNotificationsLocale([twineLang UTF8String]); + GetFramework().SetTurnNotificationsLocale(twineLang); } - (void)speakOneString:(NSString *)textToSpeak @@ -150,4 +200,59 @@ extern NSString * const kMwmTextToSpeechDisable = @"MWMTEXTTOSPEECH_DISABLE"; [self speakOneString:@(text.c_str())]; } +static vector> availableLanguages() +{ + NSArray * voices = [AVSpeechSynthesisVoice speechVoices]; + vector native(voices.count); + for (AVSpeechSynthesisVoice * v in voices) + native.push_back(tts::bcp47ToTwineLanguage(v.language)); + + sort(native.begin(), native.end()); + using namespace routing::turns::sound; + vector> result; + for (auto const & p : kLanguageList) + { + if (find(native.begin(), native.end(), p.first) != native.end()) + result.push_back(p); + } + return result; +} + @end + +namespace tts +{ + +string bcp47ToTwineLanguage(NSString const * bcp47LangName) +{ + if (bcp47LangName == nil || [bcp47LangName length] < 2) + return nil; + + if ([bcp47LangName isEqualToString:@"zh-CN"] || [bcp47LangName isEqualToString:@"zh-CHS"] + || [bcp47LangName isEqualToString:@"zh-SG"]) + { + return "zh-Hans"; // Chinese simplified + } + + if ([bcp47LangName hasPrefix:@"zh"]) + return "zh-Hant"; // Chinese traditional + + // Taking two first symbols of a language name. For example ru-RU -> ru + return [[bcp47LangName substringToIndex:2] UTF8String]; +} + +string translatedTwine(string const & twine) +{ + auto const & list = routing::turns::sound::kLanguageList; + auto const it = find_if(list.begin(), list.end(), [&twine](pair const & pair) + { + return pair.first == twine; + }); + + if (it != list.end()) + return it->second; + else + return ""; +} + +} // namespace tts \ No newline at end of file diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/Dashboard/MWMNavigationDashboard.h b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/Dashboard/MWMNavigationDashboard.h index f26d0ddad4..9b1c4afa89 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/Dashboard/MWMNavigationDashboard.h +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Views/Dashboard/MWMNavigationDashboard.h @@ -12,6 +12,7 @@ @property (weak, nonatomic) IBOutlet UILabel * arrivalsTimeLabel; @property (weak, nonatomic) IBOutlet UILabel * roundRoadLabel; @property (weak, nonatomic) IBOutlet UILabel * streetLabel; +@property (weak, nonatomic) IBOutlet UIButton * soundButton; @property (weak, nonatomic) IBOutlet UISlider * progress; - (void)configureWithEntity:(MWMNavigationDashboardEntity *)entity; diff --git a/iphone/Maps/Classes/MapViewController.mm b/iphone/Maps/Classes/MapViewController.mm index 0f1ec7580b..ba57e11d99 100644 --- a/iphone/Maps/Classes/MapViewController.mm +++ b/iphone/Maps/Classes/MapViewController.mm @@ -6,6 +6,7 @@ #import "MWMAPIBar.h" #import "MWMMapViewControlsManager.h" #import "RouteState.h" +#import "MWMTextToSpeech.h" #import "UIFont+MapsMeFonts.h" #import "UIViewController+Navigation.h" @@ -143,7 +144,7 @@ typedef NS_ENUM(NSUInteger, UserTouchesAction) if (res.IsValid()) [self.controlsManager setupRoutingDashboard:res]; - [self.controlsManager playTurnNotifications]; + [[MWMTextToSpeech tts] playTurnNotifications]; } - (void)onCompassUpdate:(location::CompassInfo const &)info diff --git a/iphone/Maps/Classes/MapsAppDelegate.mm b/iphone/Maps/Classes/MapsAppDelegate.mm index 0f8fed86a4..0e625ba65c 100644 --- a/iphone/Maps/Classes/MapsAppDelegate.mm +++ b/iphone/Maps/Classes/MapsAppDelegate.mm @@ -47,6 +47,8 @@ static NSString * const kBundleVersion = @"BundleVersion"; extern string const kCountryCodeKey; extern string const kUniqueIdKey; extern string const kLanguageKey; +extern NSString * const kUserDefaultsTTSLanguage; +extern NSString * const kUserDafaultsNeedToEnableTTS; /// Adds needed localized strings to C++ code /// @TODO Refactor localization mechanism to make it simpler @@ -202,11 +204,13 @@ void InitLocalizedStrings() [self incrementSessionCount]; [self showAlertIfRequired]; } - + Framework & f = GetFramework(); application.applicationIconBadgeNumber = f.GetCountryTree().GetActiveMapLayout().GetOutOfDateCount(); f.GetLocationState()->InvalidatePosition(); + [self enableTTSForTheFirstTime]; + return returnValue; } @@ -529,6 +533,17 @@ void InitLocalizedStrings() [RouteState remove]; } +#pragma mark - TTS + +- (void)enableTTSForTheFirstTime +{ + NSUserDefaults * ud = [NSUserDefaults standardUserDefaults]; + if ([ud stringForKey:kUserDefaultsTTSLanguage].length) + return; + [ud setBool:YES forKey:kUserDafaultsNeedToEnableTTS]; + [ud synchronize]; +} + #pragma mark - Standby - (void)enableStandby diff --git a/iphone/Maps/MWMTTSLanguageViewController.h b/iphone/Maps/MWMTTSLanguageViewController.h new file mode 100644 index 0000000000..1530c6f92a --- /dev/null +++ b/iphone/Maps/MWMTTSLanguageViewController.h @@ -0,0 +1,5 @@ +#import "TableViewController.h" + +@interface MWMTTSLanguageViewController : TableViewController + +@end diff --git a/iphone/Maps/MWMTTSLanguageViewController.mm b/iphone/Maps/MWMTTSLanguageViewController.mm new file mode 100644 index 0000000000..65aae4345c --- /dev/null +++ b/iphone/Maps/MWMTTSLanguageViewController.mm @@ -0,0 +1,39 @@ +#import "MWMTextToSpeech.h" +#import "MWMTTSLanguageViewController.h" +#import "MWMTTSSettingsViewController.h" +#import "SelectableCell.h" + +static NSString * const kUnwingSegueIdentifier = @"UnwindToTTSSettings"; + +@implementation MWMTTSLanguageViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + self.title = L(@"pref_tts_other_section_title"); +} + +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(SelectableCell *)sender +{ + if (![segue.identifier isEqualToString:kUnwingSegueIdentifier]) + return; + MWMTTSSettingsViewController * dest = segue.destinationViewController; + NSUInteger const row = [self.tableView indexPathForCell:sender].row; + [dest setAdditionalTTSLanguage:[[MWMTextToSpeech tts] availableLanguages][row]]; +} + +#pragma mark - UITableViewDataSource && UITableViewDelegate + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return [[MWMTextToSpeech tts] availableLanguages].size(); +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + SelectableCell * cell = (SelectableCell *)[tableView dequeueReusableCellWithIdentifier:[SelectableCell className]]; + cell.titleLabel.text = @([[MWMTextToSpeech tts] availableLanguages][indexPath.row].second.c_str()); + return cell; +} + +@end diff --git a/iphone/Maps/MWMTTSSettingsViewController.h b/iphone/Maps/MWMTTSSettingsViewController.h new file mode 100644 index 0000000000..29035fbe6d --- /dev/null +++ b/iphone/Maps/MWMTTSSettingsViewController.h @@ -0,0 +1,7 @@ +#import "TableViewController.h" + +@interface MWMTTSSettingsViewController : TableViewController + +- (void)setAdditionalTTSLanguage:(std::pair const &)l; + +@end diff --git a/iphone/Maps/MWMTTSSettingsViewController.mm b/iphone/Maps/MWMTTSSettingsViewController.mm new file mode 100644 index 0000000000..86545a8acd --- /dev/null +++ b/iphone/Maps/MWMTTSSettingsViewController.mm @@ -0,0 +1,134 @@ +#import +#import "LinkCell.h" +#import "MWMTextToSpeech.h" +#import "MWMTTSSettingsViewController.h" +#import "SelectableCell.h" + +static NSString * kSelectTTSLanguageSegueName = @"TTSLanguage"; + +using namespace std; + +@interface MWMTTSSettingsViewController () +{ + pair _additionalTTSLanguage; + vector> _languages; +} + +@property (nonatomic) BOOL isLocaleLanguageAbsent; + +@end + +@implementation MWMTTSSettingsViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + self.title = L(@"pref_tts_language_title"); + MWMTextToSpeech * tts = [MWMTextToSpeech tts]; + + _languages.reserve(3); + auto const & v = tts.availableLanguages; + pair const standart = v.front(); + _languages.push_back(standart); + + using namespace tts; + string const current = bcp47ToTwineLanguage([AVSpeechSynthesisVoice currentLanguageCode]); + if (current != standart.first && !current.empty()) + { + string const translated = translatedTwine(current); + pair const cur {current, translated}; + if (translated.empty() || find(v.begin(), v.end(), cur) != v.end()) + _languages.push_back(cur); + else + self.isLocaleLanguageAbsent = YES; + } + string const savedLanguage = tts.savedLanguage.UTF8String; + if (savedLanguage != current && savedLanguage != standart.first && !savedLanguage.empty()) + _languages.push_back({savedLanguage, translatedTwine(savedLanguage)}); +} + +- (IBAction)unwind:(id)sender +{ + size_t const size = _languages.size(); + switch (size) + { + case 1: + _languages.push_back(_additionalTTSLanguage); + break; + case 2: + if (self.isLocaleLanguageAbsent) + _languages[size - 1] = _additionalTTSLanguage; + else + _languages.push_back(_additionalTTSLanguage); + break; + case 3: + _languages[size - 1] = _additionalTTSLanguage; + break; + default: + NSAssert(false, @"Incorrect language's count"); + break; + } + [self.tableView reloadData]; +} + +- (void)setAdditionalTTSLanguage:(pair const &)l +{ + [[MWMTextToSpeech tts] setNotificationsLocale:l.first]; + _additionalTTSLanguage = l; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 2; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + if (section == 0) + return _languages.size() + 1; + else + return 1; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == 0 && indexPath.row != _languages.size()) + { + SelectableCell * cell = (SelectableCell *)[tableView dequeueReusableCellWithIdentifier:[SelectableCell className]]; + pair const p = _languages[indexPath.row]; + cell.titleLabel.text = @(p.second.c_str()); + BOOL const isSelected = [@(p.first.c_str()) isEqualToString:[[MWMTextToSpeech tts] savedLanguage]]; + cell.accessoryType = isSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; + return cell; + } + else + { + LinkCell * cell = (LinkCell *)[tableView dequeueReusableCellWithIdentifier:[LinkCell className]]; + cell.titleLabel.text = indexPath.section == 0 ? L(@"pref_tts_other_section_title") : L(@"pref_tts_how_to_set_up_voice"); + return cell; + } +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == 0) + { + if (indexPath.row == _languages.size()) + { + [self performSegueWithIdentifier:kSelectTTSLanguageSegueName sender:nil]; + } + else + { + [[MWMTextToSpeech tts] setNotificationsLocale:_languages[indexPath.row].first]; + [tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade]; + } + } + else + { +#warning need to add help + } +} + +@end diff --git a/iphone/Maps/Settings/SettingsViewController.mm b/iphone/Maps/Settings/SettingsViewController.mm index 9fbcb7eefa..d21999f3ca 100644 --- a/iphone/Maps/Settings/SettingsViewController.mm +++ b/iphone/Maps/Settings/SettingsViewController.mm @@ -1,4 +1,3 @@ - #import "SettingsViewController.h" #import "SwitchCell.h" #import "SelectableCell.h" @@ -8,6 +7,7 @@ #import "MapsAppDelegate.h" #import "Statistics.h" #import "MWMMapViewControlsManager.h" +#import "MWMTextToSpeech.h" #include "Framework.h" @@ -16,14 +16,16 @@ #include "platform/preferred_languages.hpp" extern char const * kStatisticsEnabledSettingsKey; +extern NSString * const kTTSStatusWasChangedNotification = @"TTFStatusWasChangedFromSettingsNotification"; typedef NS_ENUM(NSUInteger, Section) { SectionMetrics, SectionZoomButtons, + SectionRouting, SectionCalibration, SectionStatistics, - SectionCount + SectionCount // Must be latest value! }; @interface SettingsViewController () @@ -60,7 +62,7 @@ typedef NS_ENUM(NSUInteger, Section) - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (section == SectionMetrics) + if (section == SectionMetrics || section == SectionRouting) return 2; else return 1; @@ -110,7 +112,23 @@ typedef NS_ENUM(NSUInteger, Section) customCell.titleLabel.text = L(@"pref_calibration_title"); customCell.delegate = self; } - + else if (indexPath.section == SectionRouting) + { + if (indexPath.row == 0) + { + cell = [tableView dequeueReusableCellWithIdentifier:[SwitchCell className]]; + SwitchCell * customCell = (SwitchCell *)cell; + customCell.switchButton.on = [[MWMTextToSpeech tts] isNeedToEnable]; + customCell.titleLabel.text = L(@"pref_tts_enable_title"); + customCell.delegate = self; + } + else + { + cell = [tableView dequeueReusableCellWithIdentifier:[LinkCell className]]; + LinkCell * customCell = (LinkCell *)cell; + customCell.titleLabel.text = L(@"pref_tts_language_title"); + } + } return cell; } @@ -144,6 +162,13 @@ typedef NS_ENUM(NSUInteger, Section) { Settings::Set("CompassCalibrationEnabled", (bool)value); } + else if (indexPath.section == SectionRouting) + { + [[MWMTextToSpeech tts] setNeedToEnable:value]; + [[NSNotificationCenter defaultCenter] postNotificationName:kTTSStatusWasChangedNotification + object:nil + userInfo:@{@"on" : @(value)}]; + } } Settings::Units unitsForIndex(NSInteger index) @@ -166,6 +191,8 @@ Settings::Units unitsForIndex(NSInteger index) { if (section == SectionMetrics) return L(@"measurement_units"); + else if (section == SectionRouting) + return L(@"prefs_group_route"); else return nil; }