From 7d93d1459a085bb498cf9a7e4f3680f2d7e08839 Mon Sep 17 00:00:00 2001 From: vermont42 Date: Sat, 22 Jan 2022 14:20:03 -0800 Subject: [PATCH] make accessible --- Conjuguer.xcodeproj/project.pbxproj | 8 + Conjuguer/App/ConjuguerApp.swift | 1 + Conjuguer/Assets/en.lproj/Localizable.strings | 13 +- Conjuguer/Assets/fr.lproj/Localizable.strings | 12 +- Conjuguer/Info.plist | 2 + Conjuguer/Models/ImageInfo.swift | 2 +- Conjuguer/Models/Info.swift | 52 +-- Conjuguer/Models/L.swift | 38 ++- Conjuguer/Models/PersonNumber.swift | 59 +++- Conjuguer/Models/Quiz.swift | 33 ++ Conjuguer/Models/Tense.swift | 18 + Conjuguer/Models/Verb.swift | 24 ++ Conjuguer/Utils/ConjugationText.swift | 10 +- Conjuguer/Utils/Modifiers.swift | 35 +- Conjuguer/Utils/Utterer.swift | 37 ++ Conjuguer/Views/InfoBrowseView.swift | 1 + Conjuguer/Views/InfoView.swift | 5 +- Conjuguer/Views/ModelBrowseView.swift | 4 +- Conjuguer/Views/ModelView.swift | 60 +++- Conjuguer/Views/QuizView.swift | 4 + Conjuguer/Views/VerbBrowseView.swift | 8 +- Conjuguer/Views/VerbView.swift | 322 +++++++++++++++--- 22 files changed, 641 insertions(+), 107 deletions(-) create mode 100644 Conjuguer/Utils/Utterer.swift diff --git a/Conjuguer.xcodeproj/project.pbxproj b/Conjuguer.xcodeproj/project.pbxproj index 7f1e358..ca31d7b 100644 --- a/Conjuguer.xcodeproj/project.pbxproj +++ b/Conjuguer.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 030FFAA526CA19CB00741524 /* frequencies.xml in Resources */ = {isa = PBXBuildFile; fileRef = 030FFAA426CA19CB00741524 /* frequencies.xml */; }; 030FFAA726CA1A2000741524 /* FrequencyParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030FFAA626CA1A2000741524 /* FrequencyParser.swift */; }; + 031D6454278B4C890000C7DB /* Utterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031D6453278B4C890000C7DB /* Utterer.swift */; }; 032B122326BA02D8000E70CA /* InfoBrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032B122226BA02D8000E70CA /* InfoBrowseView.swift */; }; 032B122726BB3FA0000E70CA /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032B122626BB3FA0000E70CA /* ColorExtension.swift */; }; 032D267226BDF52100AE1BD1 /* InfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032D267126BDF52100AE1BD1 /* InfoView.swift */; }; @@ -74,6 +75,7 @@ 036DB80525A386D60013C3B7 /* VerbModelParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036DB80425A386D60013C3B7 /* VerbModelParser.swift */; }; 036DB81025A6C7900013C3B7 /* PasseSimpleGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036DB80F25A6C7900013C3B7 /* PasseSimpleGroup.swift */; }; 036DB81825A8C9420013C3B7 /* IndicatifPresentGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036DB81725A8C9420013C3B7 /* IndicatifPresentGroup.swift */; }; + 036DEF2B2782226A00B0BAED /* ImageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036DEF2A2782226A00B0BAED /* ImageInfo.swift */; }; 036E4D102735CA7C00A52973 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 036E4D0F2735CA7C00A52973 /* LaunchScreen.storyboard */; }; 036F9872276F929200ACD986 /* RatingsFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036F9871276F929200ACD986 /* RatingsFetcher.swift */; }; 036F9874276FDDC500ACD986 /* URLProtocolStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036F9873276FDDC500ACD986 /* URLProtocolStub.swift */; }; @@ -135,6 +137,7 @@ /* Begin PBXFileReference section */ 030FFAA426CA19CB00741524 /* frequencies.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = frequencies.xml; sourceTree = ""; }; 030FFAA626CA1A2000741524 /* FrequencyParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrequencyParser.swift; sourceTree = ""; }; + 031D6453278B4C890000C7DB /* Utterer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utterer.swift; sourceTree = ""; }; 032B122226BA02D8000E70CA /* InfoBrowseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoBrowseView.swift; sourceTree = ""; }; 032B122626BB3FA0000E70CA /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; }; 032D267126BDF52100AE1BD1 /* InfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoView.swift; sourceTree = ""; }; @@ -205,6 +208,7 @@ 036DB80425A386D60013C3B7 /* VerbModelParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbModelParser.swift; sourceTree = ""; }; 036DB80F25A6C7900013C3B7 /* PasseSimpleGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasseSimpleGroup.swift; sourceTree = ""; }; 036DB81725A8C9420013C3B7 /* IndicatifPresentGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndicatifPresentGroup.swift; sourceTree = ""; }; + 036DEF2A2782226A00B0BAED /* ImageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageInfo.swift; sourceTree = ""; }; 036E4D0F2735CA7C00A52973 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 036F9871276F929200ACD986 /* RatingsFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingsFetcher.swift; sourceTree = ""; }; 036F9873276FDDC500ACD986 /* URLProtocolStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLProtocolStub.swift; sourceTree = ""; }; @@ -365,6 +369,7 @@ 030FFAA426CA19CB00741524 /* frequencies.xml */, 030FFAA626CA1A2000741524 /* FrequencyParser.swift */, 03ACAA0025ACB65300CF5390 /* FuturSimple.swift */, + 036DEF2A2782226A00B0BAED /* ImageInfo.swift */, 03ACA9F625A9FD4400CF5390 /* Imparfait.swift */, 036DB81725A8C9420013C3B7 /* IndicatifPresentGroup.swift */, 03309F6526BC9C95003E4F92 /* Info.swift */, @@ -430,6 +435,7 @@ 036F9875276FDE0000ACD986 /* URLSessionExtension.swift */, 0373682F25DC672F008BDF46 /* UserDefaultsGetterSetter.swift */, 0373681A25DB8BAD008BDF46 /* Util.swift */, + 031D6453278B4C890000C7DB /* Utterer.swift */, 0373682925DC667B008BDF46 /* VerbSort.swift */, 0373681F25DC6502008BDF46 /* World.swift */, ); @@ -653,6 +659,7 @@ 03AE93F3274443E400233105 /* StubAnalyticsLocale.swift in Sources */, 03AE93ED2744424F00233105 /* DeviceUtility.swift in Sources */, 038E0AFE2690976D002A71EF /* DefectGroup.swift in Sources */, + 036DEF2B2782226A00B0BAED /* ImageInfo.swift in Sources */, 0338F16F273F6A7300FD796C /* GameCenter.swift in Sources */, 03C2CAC62619125200CFA825 /* Sound.swift in Sources */, 036DB7DF25A0DD7A0013C3B7 /* Verb.swift in Sources */, @@ -674,6 +681,7 @@ 03ACAA0625ACB6B900CF5390 /* ConditionnelPresent.swift in Sources */, 03309F6C26BCA0B0003E4F92 /* UIFontExtension.swift in Sources */, 036DB7FB25A13C110013C3B7 /* ConjugatorError.swift in Sources */, + 031D6454278B4C890000C7DB /* Utterer.swift in Sources */, 03ACA9F725A9FD4400CF5390 /* Imparfait.swift in Sources */, 03309F6A26BC9E72003E4F92 /* StringExtensions.swift in Sources */, 036A99382726168900C6C984 /* TestGameCenter.swift in Sources */, diff --git a/Conjuguer/App/ConjuguerApp.swift b/Conjuguer/App/ConjuguerApp.swift index 28e491e..54c04f6 100644 --- a/Conjuguer/App/ConjuguerApp.swift +++ b/Conjuguer/App/ConjuguerApp.swift @@ -29,6 +29,7 @@ struct ConjuguerApp: App { Modifiers.modifyAppearances() SoundPlayer.setup() + Utterer.setup() VerbModel.models = VerbModelParser().parse() Verb.verbs = VerbParser().parse() diff --git a/Conjuguer/Assets/en.lproj/Localizable.strings b/Conjuguer/Assets/en.lproj/Localizable.strings index 4269a28..a48ecb2 100644 --- a/Conjuguer/Assets/en.lproj/Localizable.strings +++ b/Conjuguer/Assets/en.lproj/Localizable.strings @@ -17,12 +17,11 @@ "Navigation.back" = "Back"; "VerbView.overview" = "Overview"; -"VerbView.model" = "Model:"; +"VerbView.modelWithColon" = "Model:"; "VerbView.reflexive" = "Reflexive"; "VerbView.aspiratedH" = "Aspirated H"; -"VerbView.auxiliaryÊtre" = "Auxiliary: être"; -"VerbView.auxiliaryAvoir" = "Auxiliary: avoir"; -"VerbView.frequency" = "Frequency"; +"VerbView.auxiliaryWithColon" = "Auxiliary:"; +"VerbView.frequencyWithColon" = "Frequency:"; "VerbView.defective" = "Defective."; "VerbView.exampleUse" = "Example Use"; "VerbView.personlessConjugations" = "P. Passé, P. Présent, R. Futur"; @@ -36,6 +35,7 @@ "ModelView.irregular" = "Irregular"; "ModelView.verbsUsing" = "Verbs Using This Model"; "ModelView.verbUsing" = "Verb Using This Model"; +"ModelView.infoButtonHint" = "shows infomation about how verb conjugation irregularities are represented"; "QuizView.start" = "Start"; "QuizView.scoreWithColon" = "Score:"; @@ -44,6 +44,8 @@ "QuizView.pronounWithColon" = "Pronoun:"; "QuizView.tenseWithColon" = "Tense:"; "QuizView.progressWithColon" = "Progress:"; +"QuizView.outOf" = "out of"; +"QuizView.seconds" = "seconds"; "QuizView.elapsedWithColon" = "Elapsed:"; "QuizView.conjugation" = "Conjugation"; "QuizView.quit" = "Quit"; @@ -1114,6 +1116,9 @@ Machio Okada and Hitoshi Oguriso created the Tableaux de conjugaison de l’anci "RatingsFetcher.oneRating" = "There is one rating for this version of Conjuguer."; "RatingsFetcher.multipleRatings" = "There are %d ratings for this version of Conjuguer."; +"ImageInfo.davidCompton" = "photograph of David Compton taken in Paris, France during the early 1960s"; +"ImageInfo.joshAdams" = "photograph of Josh Adams taken near Vacaville, California in 2021"; + "VerbSort.frequency" = "Frequency"; "ModelSort.irregularity" = "Irregularity"; diff --git a/Conjuguer/Assets/fr.lproj/Localizable.strings b/Conjuguer/Assets/fr.lproj/Localizable.strings index 0b491c8..a32f4aa 100644 --- a/Conjuguer/Assets/fr.lproj/Localizable.strings +++ b/Conjuguer/Assets/fr.lproj/Localizable.strings @@ -17,12 +17,12 @@ "Navigation.back" = "Retourner"; "VerbView.overview" = "Aperçu"; -"VerbView.model" = "Modèle:"; +"VerbView.modelWithColon" = "Modèle:"; "VerbView.reflexive" = "Réflexif"; "VerbView.aspiratedH" = "H Aspirée"; "VerbView.auxiliaryÊtre" = "Auxiliaire: Être"; -"VerbView.auxiliaryAvoir" = "Auxiliaire: Avoir"; -"VerbView.frequency" = "Fréquence"; +"VerbView.auxiliaryWithColon" = "Auxiliaire:"; +"VerbView.frequencyWithColon" = "Fréquence:"; "VerbView.defective" = "Défectif."; "VerbView.exampleUse" = "Exemple d’utilisation"; "VerbView.personlessConjugations" = "P. Passé, P. Présent, R. Futur"; @@ -36,6 +36,7 @@ "ModelView.irregular" = "Irrégulier"; "ModelView.verbsUsing" = "Verbes qui utilisent ce modèle"; "ModelView.verbUsing" = "Verbe qui utilise ce modèle"; +"ModelView.infoButtonHint" = "montre des informations sur la façon dont les irrégularités de conjugaison des verbes sont représentées"; "QuizView.start" = "Commencer"; "QuizView.scoreWithColon" = "Score:"; @@ -44,6 +45,8 @@ "QuizView.pronounWithColon" = "Pronom:"; "QuizView.tenseWithColon" = "Temps:"; "QuizView.progressWithColon" = "Progrès:"; +"QuizView.outOf" = "sur"; +"QuizView.seconds" = "secondes"; "QuizView.elapsedWithColon" = "Écoulé:"; "QuizView.conjugation" = "Conjugaison"; "QuizView.quit" = "Quitter"; @@ -1114,6 +1117,9 @@ Machio Okada et Hitoshi Oguriso ont créé les Tableaux de conjugaison de l’an "RatingsFetcher.oneRating" = "Cette version de Conjuguer n’a qu’une évaluation."; "RatingsFetcher.multipleRatings" = "Cette version de Conjuguer a %d évaluations."; +"ImageInfo.davidCompton" = "photographie de David Compton prise à Paris, en France, au début des années 1960"; +"ImageInfo.joshAdams" = "photographie de Josh Adams prise près de Vacaville, en Californie, en 2021"; + "VerbSort.frequency" = "Fréquence"; "ModelSort.irregularity" = "Irrégularité"; diff --git a/Conjuguer/Info.plist b/Conjuguer/Info.plist index 2ea5b54..2c819f0 100644 --- a/Conjuguer/Info.plist +++ b/Conjuguer/Info.plist @@ -18,6 +18,8 @@ $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString $(MARKETING_VERSION) + CFBundleSpokenName + con jew gay CFBundleURLTypes diff --git a/Conjuguer/Models/ImageInfo.swift b/Conjuguer/Models/ImageInfo.swift index 937e3bb..4b7f6ff 100644 --- a/Conjuguer/Models/ImageInfo.swift +++ b/Conjuguer/Models/ImageInfo.swift @@ -7,7 +7,7 @@ import Foundation -struct ImageInfo { +struct ImageInfo: Hashable { let filename: String let accessibilityLabel: String } diff --git a/Conjuguer/Models/Info.swift b/Conjuguer/Models/Info.swift index 9f24d5f..e79e661 100644 --- a/Conjuguer/Models/Info.swift +++ b/Conjuguer/Models/Info.swift @@ -10,42 +10,44 @@ import Foundation struct Info: Hashable { let heading: String let attributedText: NSAttributedString - let imageName: String? + let alwaysUsesFrenchPronunciation: Bool + let imageInfo: ImageInfo? - private init(heading: String, text: String, imageName: String? = nil) { + private init(heading: String, text: String, alwaysUsesFrenchPronunciation: Bool = false, imageInfo: ImageInfo? = nil) { self.heading = heading attributedText = text.attributedText - self.imageName = imageName + self.alwaysUsesFrenchPronunciation = alwaysUsesFrenchPronunciation + self.imageInfo = imageInfo } static let infos: [Info] = [ - Info(heading: L.Info.dedicationHeading, text: L.Info.dedicationText, imageName: "Compton"), + Info(heading: L.Info.dedicationHeading, text: L.Info.dedicationText, imageInfo: ImageInfo(filename: "Compton", accessibilityLabel: L.ImageInfo.davidCompton)), Info(heading: L.Info.valuePropositionHeading, text: L.Info.valuePropositionText), Info(heading: L.Info.terminologyHeading, text: L.Info.terminologyText), Info(heading: L.Info.irregularitiesHeading, text: L.Info.irregularitiesText), Info(heading: L.Info.defectivenessHeading, text: L.Info.defectivenessText), - Info(heading: L.Info.participePasséHeading, text: L.Info.participePasséText), - Info(heading: L.Info.participePrésentHeading, text: L.Info.participePrésentText), - Info(heading: L.Info.radicalFuturHeading, text: L.Info.radicalFuturText), - Info(heading: L.Info.indicatifPrésentHeading, text: L.Info.indicatifPrésentText), - Info(heading: L.Info.passéSimpleHeading, text: L.Info.passéSimpleText), - Info(heading: L.Info.imparfaitHeading, text: L.Info.imparfaitText), - Info(heading: L.Info.futurSimpleHeading, text: L.Info.futurSimpleText), - Info(heading: L.Info.conditionnelPrésentHeading, text: L.Info.conditionnelPrésentText), - Info(heading: L.Info.subjonctifPrésentHeading, text: L.Info.subjonctifPrésentText), - Info(heading: L.Info.subjonctifImparfaitHeading, text: L.Info.subjonctifImparfaitText), - Info(heading: L.Info.impératifHeading, text: L.Info.impératifText), - Info(heading: L.Info.passéComposéHeading, text: L.Info.passéComposéText), - Info(heading: L.Info.plusQueParfaitHeading, text: L.Info.plusQueParfaitText), - Info(heading: L.Info.passéAntérieurHeading, text: L.Info.passéAntérieurText), - Info(heading: L.Info.passéSurcomposéHeading, text: L.Info.passéSurcomposéText), - Info(heading: L.Info.futurAntérieurHeading, text: L.Info.futurAntérieurText), - Info(heading: L.Info.conditionnelPasséHeading, text: L.Info.conditionnelPasséText), - Info(heading: L.Info.subjonctifPasséHeading, text: L.Info.subjonctifPasséText), - Info(heading: L.Info.subjonctifPlusQueParfaitHeading, text: L.Info.subjonctifPlusQueParfaitText), - Info(heading: L.Info.impératifPasséHeading, text: L.Info.impératifPasséText), + Info(heading: L.Info.participePasséHeading, text: L.Info.participePasséText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.participePrésentHeading, text: L.Info.participePrésentText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.radicalFuturHeading, text: L.Info.radicalFuturText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.indicatifPrésentHeading, text: L.Info.indicatifPrésentText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.passéSimpleHeading, text: L.Info.passéSimpleText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.imparfaitHeading, text: L.Info.imparfaitText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.futurSimpleHeading, text: L.Info.futurSimpleText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.conditionnelPrésentHeading, text: L.Info.conditionnelPrésentText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.subjonctifPrésentHeading, text: L.Info.subjonctifPrésentText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.subjonctifImparfaitHeading, text: L.Info.subjonctifImparfaitText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.impératifHeading, text: L.Info.impératifText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.passéComposéHeading, text: L.Info.passéComposéText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.plusQueParfaitHeading, text: L.Info.plusQueParfaitText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.passéAntérieurHeading, text: L.Info.passéAntérieurText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.passéSurcomposéHeading, text: L.Info.passéSurcomposéText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.futurAntérieurHeading, text: L.Info.futurAntérieurText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.conditionnelPasséHeading, text: L.Info.conditionnelPasséText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.subjonctifPasséHeading, text: L.Info.subjonctifPasséText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.subjonctifPlusQueParfaitHeading, text: L.Info.subjonctifPlusQueParfaitText, alwaysUsesFrenchPronunciation: true), + Info(heading: L.Info.impératifPasséHeading, text: L.Info.impératifPasséText, alwaysUsesFrenchPronunciation: true), Info(heading: L.Info.questionsAndResponsesHeading, text: L.Info.questionsAndResponsesText), - Info(heading: L.Info.creditsHeading, text: L.Info.creditsText, imageName: "Adams") + Info(heading: L.Info.creditsHeading, text: L.Info.creditsText, imageInfo: ImageInfo(filename: "Adams", accessibilityLabel: L.ImageInfo.joshAdams)) ] static func headingToIndex(heading: String) -> Int? { diff --git a/Conjuguer/Models/L.swift b/Conjuguer/Models/L.swift index 52429cb..7b96def 100644 --- a/Conjuguer/Models/L.swift +++ b/Conjuguer/Models/L.swift @@ -47,8 +47,8 @@ enum L { String(localized: "VerbView.overview") } - static var model: String { - String(localized: "VerbView.model") + static var modelWithColon: String { + String(localized: "VerbView.modelWithColon") } static var reflexive: String { @@ -59,16 +59,12 @@ enum L { String(localized: "VerbView.aspiratedH") } - static var auxiliaryÊtre: String { - String(localized: "VerbView.auxiliaryÊtre") + static var auxiliaryWithColon: String { + String(localized: "VerbView.auxiliaryWithColon") } - static var auxiliaryAvoir: String { - String(localized: "VerbView.auxiliaryAvoir") - } - - static var frequency: String { - String(localized: "VerbView.frequency") + static var frequencyWithColon: String { + String(localized: "VerbView.frequencyWithColon") } static var defective: String { @@ -130,6 +126,10 @@ enum L { static var verbUsing: String { String(localized: "ModelView.verbUsing") } + + static var infoButtonHint: String { + String(localized: "ModelView.infoButtonHint") + } } enum QuizView { @@ -157,6 +157,14 @@ enum L { String(localized: "QuizView.progressWithColon") } + static var outOf: String { + String(localized: "QuizView.outOf") + } + + static var seconds: String { + String(localized: "QuizView.seconds") + } + static var scoreWithColon: String { String(localized: "QuizView.scoreWithColon") } @@ -492,6 +500,16 @@ enum L { } } + enum ImageInfo { + static var davidCompton: String { + String(localized: "ImageInfo.davidCompton") + } + + static var joshAdams: String { + String(localized: "ImageInfo.joshAdams") + } + } + static func displayNameForVerbSort(_ sort: VerbSort) -> String { switch sort { case .frequency: diff --git a/Conjuguer/Models/PersonNumber.swift b/Conjuguer/Models/PersonNumber.swift index 138b633..23b0103 100644 --- a/Conjuguer/Models/PersonNumber.swift +++ b/Conjuguer/Models/PersonNumber.swift @@ -17,19 +17,33 @@ enum PersonNumber: String, CaseIterable { static let impératifPersonNumbers: [PersonNumber] = [.secondSingular, .firstPlural, .secondPlural] var pronoun: String { + let pronounGender = Current.settings.pronounGender + let singular = " singulier" // Intentionally not localizing this. + let plural = " pluriel" + switch self { case .firstSingular: return "je" case .secondSingular: return "tu" case .thirdSingular: - return "il" + switch pronounGender { + case .feminine, .both: + return "elle" + singular + case .masculine: + return "il" + singular + } case .firstPlural: return "nous" case .secondPlural: return "vous" case .thirdPlural: - return "ils" + switch pronounGender { + case .masculine, .both: + return "ils" + plural + case .feminine: + return "elles" + plural + } } } @@ -84,6 +98,47 @@ enum PersonNumber: String, CaseIterable { } } + var gender: String { + let pronounGender = Current.settings.pronounGender + let masc = L.PronounGender.masculine + let fem = L.PronounGender.feminine + + switch self { + case .firstSingular: + switch pronounGender { + case .feminine, .both: + return fem + case .masculine: + return masc + } + case .secondSingular: + switch pronounGender { + case .feminine, .both: + return fem + case .masculine: + return masc + } + case .thirdSingular: + return "" // Gender is implied by pronoun itself. + case .firstPlural: + switch pronounGender { + case .masculine, .both: + return masc + case .feminine: + return fem + } + case .secondPlural: + switch pronounGender { + case .masculine, .both: + return masc + case .feminine: + return fem + } + case .thirdPlural: + return "" // Gender is implied by pronoun lui-même. + } + } + var shortDisplayName: String { switch self { case .firstSingular: diff --git a/Conjuguer/Models/Quiz.swift b/Conjuguer/Models/Quiz.swift index 5f9b709..b955496 100644 --- a/Conjuguer/Models/Quiz.swift +++ b/Conjuguer/Models/Quiz.swift @@ -61,6 +61,7 @@ class Quiz: ObservableObject { resetPublishedProperties() buildQuiz() quizState = .inProgress + announcePublishedProperties() SoundPlayer.play(Sound.randomGun) Current.analytics.recordQuizStart(difficulty: Current.settings.quizDifficulty) @@ -330,6 +331,8 @@ class Quiz: ObservableObject { if currentQuestionIndex == questions.count { completeQuiz() + } else { + announcePublishedProperties() } } @@ -345,6 +348,36 @@ class Quiz: ObservableObject { quit() } + private func announcePublishedProperties() { + if UIAccessibility.isVoiceOverRunning { + let announcementDelay = 1.0 + DispatchQueue.main.asyncAfter(deadline: .now() + announcementDelay) { [self] in + let currentLocaleString: String + let currentRegion = Current.analytics.analyticsLocale.regionCode + let currentLanguage = Current.analytics.analyticsLocale.languageCode + if currentLanguage == "fr" { + currentLocaleString = Utterer.frenchLocaleString + } else { + currentLocaleString = currentLanguage + "-" + currentRegion + } + Utterer.utter(L.QuizView.verbWithColon, localeString: currentLocaleString) + let frenchLocaleString = Utterer.frenchLocaleString + Utterer.utter(questions[currentQuestionIndex].0.infinitif, localeString: frenchLocaleString) + Utterer.utter(L.QuizView.translationWithColon, localeString: currentLocaleString) + Utterer.utter(questions[currentQuestionIndex].0.translation, localeString: Utterer.englishLocaleString) + Utterer.utter(L.QuizView.pronounWithColon, localeString: currentLocaleString) + Utterer.utter(questions[currentQuestionIndex].1.pronoun, localeString: Utterer.frenchLocaleString) + Utterer.utter(questions[currentQuestionIndex].1.gender, localeString: currentLocaleString) + Utterer.utter(L.QuizView.tenseWithColon, localeString: currentLocaleString) + Utterer.utter(questions[currentQuestionIndex].1.titleCaseName, localeString: Utterer.frenchLocaleString) + Utterer.utter(L.QuizView.progressWithColon, localeString: currentLocaleString) + Utterer.utter("\(currentQuestionIndex + 1) \(L.QuizView.outOf) \(questions.count)", localeString: currentLocaleString) + Utterer.utter("\(L.QuizView.scoreWithColon) \(score)", localeString: currentLocaleString) + Utterer.utter("\(L.QuizView.elapsedWithColon) \(elapsedTime) \(L.QuizView.seconds)", localeString: currentLocaleString) + } + } + } + private static func bonusForElapsedTime(_ elapsedTime: Int) -> Int { switch elapsedTime { case 0 ... 120: diff --git a/Conjuguer/Models/Tense.swift b/Conjuguer/Models/Tense.swift index 5c9a989..9a91c50 100644 --- a/Conjuguer/Models/Tense.swift +++ b/Conjuguer/Models/Tense.swift @@ -174,6 +174,24 @@ enum Tense: Hashable { } } + var pronoun: String { + switch self { + case .indicatifPrésent(let personNumber), .passéSimple(let personNumber), .imparfait(let personNumber), .futurSimple(let personNumber), .conditionnelPrésent(let personNumber), .subjonctifPrésent(let personNumber), .subjonctifImparfait(let personNumber), .impératif(let personNumber), .passéComposé(let personNumber), .plusQueParfait(let personNumber), .passéAntérieur(let personNumber), .passéSurcomposé(let personNumber), .futurAntérieur(let personNumber), .conditionnelPassé(let personNumber), .subjonctifPassé(let personNumber), .subjonctifPlusQueParfait(let personNumber), .impératifPassé(let personNumber): + return personNumber.pronoun + case .participePassé, .participePrésent, .radicalFutur: + return L.QuizView.none + } + } + + var gender: String { + switch self { + case .indicatifPrésent(let personNumber), .passéSimple(let personNumber), .imparfait(let personNumber), .futurSimple(let personNumber), .conditionnelPrésent(let personNumber), .subjonctifPrésent(let personNumber), .subjonctifImparfait(let personNumber), .impératif(let personNumber), .passéComposé(let personNumber), .plusQueParfait(let personNumber), .passéAntérieur(let personNumber), .passéSurcomposé(let personNumber), .futurAntérieur(let personNumber), .conditionnelPassé(let personNumber), .subjonctifPassé(let personNumber), .subjonctifPlusQueParfait(let personNumber), .impératifPassé(let personNumber): + return personNumber.gender + case .participePassé, .participePrésent, .radicalFutur: + return L.QuizView.none + } + } + var pronounDecorator: String { switch self { case .indicatifPrésent(let personNumber), .passéSimple(let personNumber), .imparfait(let personNumber), .futurSimple(let personNumber), .conditionnelPrésent(let personNumber), .subjonctifPrésent(let personNumber), .subjonctifImparfait(let personNumber), .impératif(let personNumber), .passéComposé(let personNumber), .plusQueParfait(let personNumber), .passéAntérieur(let personNumber), .passéSurcomposé(let personNumber), .futurAntérieur(let personNumber), .conditionnelPassé(let personNumber), .subjonctifPassé(let personNumber), .subjonctifPlusQueParfait(let personNumber), .impératifPassé(let personNumber): diff --git a/Conjuguer/Models/Verb.swift b/Conjuguer/Models/Verb.swift index 46cba8e..34826e2 100644 --- a/Conjuguer/Models/Verb.swift +++ b/Conjuguer/Models/Verb.swift @@ -45,6 +45,30 @@ struct Verb: Identifiable, Hashable { } } + var personlessConjugations: String { + let passéPartResult = Conjugator.conjugate(infinitif: infinitif, tense: .participePassé, extraLetters: extraLetters) + let présentPartResult = Conjugator.conjugate(infinitif: infinitif, tense: .participePrésent, extraLetters: extraLetters) + let futurPartResult = Conjugator.conjugate(infinitif: infinitif, tense: .radicalFutur, extraLetters: extraLetters) + + switch passéPartResult { + case .success(let passéPart): + switch présentPartResult { + case .success(let présentPart): + switch futurPartResult { + case .success(let futurPart): + let mixedCaseConjugations = passéPart + ", " + présentPart + ", " + futurPart + return mixedCaseConjugations.lowercased() + case .failure: + return "" + } + case .failure: + return "" + } + case .failure: + return "" + } + } + static func endingIsValid(infinitif: String) -> Bool { let frenchVerbEndingLength = 2 let validFrenchVerbEndings = ["er", "ir", "re", "ïr"] diff --git a/Conjuguer/Utils/ConjugationText.swift b/Conjuguer/Utils/ConjugationText.swift index 85b9e6b..7db0d33 100644 --- a/Conjuguer/Utils/ConjugationText.swift +++ b/Conjuguer/Utils/ConjugationText.swift @@ -8,7 +8,7 @@ import SwiftUI extension Text { - init(verb: Verb, tense: Tense) { + init(verb: Verb, tense: Tense, shouldShowIrregularities: Bool = true) { var conjugation: String switch Conjugator.conjugate(infinitif: verb.infinitif, tense: tense, extraLetters: verb.extraLetters) { case .success(let value): @@ -60,14 +60,18 @@ extension Text { break } - self.init(mixedCaseString: conjugation) + if shouldShowIrregularities { + self.init(mixedCaseString: conjugation) + } else { + self.init(conjugation.lowercased()) + } if let defectGroupId = verb.defectGroupId, let defectGroup = DefectGroup.defectGroups[defectGroupId], defectGroup.isDefectiveForTense(tense) { - self = self.strikethrough() + self = strikethrough() } } diff --git a/Conjuguer/Utils/Modifiers.swift b/Conjuguer/Utils/Modifiers.swift index 0726dfc..1433886 100644 --- a/Conjuguer/Utils/Modifiers.swift +++ b/Conjuguer/Utils/Modifiers.swift @@ -63,6 +63,30 @@ struct TableText: ViewModifier { } } +struct FrenchPronunciation: ViewModifier { + let forReal: Bool + + init(forReal: Bool) { + self.forReal = forReal + } + + func body(content: Content) -> some View { + if forReal { + content + .environment(\.locale, .init(identifier: "fr-FR")) + } else { + content + } + } +} + +struct EnglishPronunciation: ViewModifier { + func body(content: Content) -> some View { + content + .environment(\.locale, .init(identifier: "en-US")) + } +} + struct BodyLabel: ViewModifier { func body(content: Content) -> some View { content @@ -107,7 +131,8 @@ struct ButtonLabel: ViewModifier { struct HeadingLabel: ViewModifier { func body(content: Content) -> some View { content - .font(Font.custom(workSansSemiBold, size: 24)) + .font(Font.custom(workSansSemiBold, size: 22)) + .accessibility(addTraits: [.isHeader]) } } @@ -159,6 +184,14 @@ extension View { modifier(TableText()) } + func frenchPronunciation(forReal: Bool = true) -> some View { + modifier(FrenchPronunciation(forReal: forReal)) + } + + func englishPronunciation() -> some View { + modifier(EnglishPronunciation()) + } + func subheadingLabel() -> some View { modifier(SubheadingLabel()) } diff --git a/Conjuguer/Utils/Utterer.swift b/Conjuguer/Utils/Utterer.swift new file mode 100644 index 0000000..ed24d43 --- /dev/null +++ b/Conjuguer/Utils/Utterer.swift @@ -0,0 +1,37 @@ +// +// Utterer.swift +// Conjuguer +// +// Created by Josh Adams on 11/18/15. +// Copyright © 2015 Josh Adams. All rights reserved. +// + +import AVFoundation + +enum Utterer { + private static let synth = AVSpeechSynthesizer() + private static let rate: Float = 0.5 + private static let pitchMultiplier: Float = 0.8 + static let defaultLocaleString = "en-US" + static let englishLocaleString = "en-US" + static let frenchLocaleString = "fr-FR" + + static func setup() { + let session = AVAudioSession.sharedInstance() + do { + try session.setCategory(.playback, options: .mixWithOthers) + } catch let error as NSError { + print("\(error.localizedDescription)") + } + utter("", localeString: defaultLocaleString) + } + + static func utter(_ thingToUtter: String, localeString: String) { + let utterance = AVSpeechUtterance(string: thingToUtter) + utterance.rate = Utterer.rate + utterance.voice = AVSpeechSynthesisVoice(language: localeString) + utterance.pitchMultiplier = Utterer.pitchMultiplier + synth.speak(utterance) + SoundPlayer.play(.silence) // https://forums.developer.apple.com/thread/23160 + } +} diff --git a/Conjuguer/Views/InfoBrowseView.swift b/Conjuguer/Views/InfoBrowseView.swift index 4f6bea0..531357f 100644 --- a/Conjuguer/Views/InfoBrowseView.swift +++ b/Conjuguer/Views/InfoBrowseView.swift @@ -31,6 +31,7 @@ struct InfoBrowseView: View { } } .buttonStyle(PlainButtonStyle()) + .frenchPronunciation(forReal: info.alwaysUsesFrenchPronunciation) } } .navigationBarTitle(L.Navigation.info) diff --git a/Conjuguer/Views/InfoView.swift b/Conjuguer/Views/InfoView.swift index c8388d0..af0b070 100644 --- a/Conjuguer/Views/InfoView.swift +++ b/Conjuguer/Views/InfoView.swift @@ -23,11 +23,12 @@ struct InfoView: View { .ignoresSafeArea() VStack { - if let imageName = info.imageName { - Image(imageName) + if let imageInfo = info.imageInfo { + Image(imageInfo.filename) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 270) + .accessibilityLabel(imageInfo.accessibilityLabel) } if shouldShowInfoHeading { diff --git a/Conjuguer/Views/ModelBrowseView.swift b/Conjuguer/Views/ModelBrowseView.swift index cf74007..a3ec929 100644 --- a/Conjuguer/Views/ModelBrowseView.swift +++ b/Conjuguer/Views/ModelBrowseView.swift @@ -36,6 +36,7 @@ struct ModelBrowseView: View { .tableText() }) .buttonStyle(PlainButtonStyle()) + .frenchPronunciation() } .navigationBarTitle(L.Navigation.models) } @@ -44,10 +45,11 @@ struct ModelBrowseView: View { } .navigationViewStyle(StackNavigationViewStyle()) // https://stackoverflow.com/a/66024249 .padding() - .searchable(text: $searchText, suggestions: { + .searchable(text: $searchText, prompt: "", suggestions: { ForEach(searchResults, id: \.self) { modelAndDecorator in Text("\(modelAndDecorator.model.exemplarWithPossibleExtraLetters)\(modelAndDecorator.decorator)") .tableText() + .frenchPronunciation() .buttonStyle(PlainButtonStyle()) .searchCompletion(modelAndDecorator.model.exemplarWithPossibleExtraLetters) } diff --git a/Conjuguer/Views/ModelView.swift b/Conjuguer/Views/ModelView.swift index 63114c2..2c153b1 100644 --- a/Conjuguer/Views/ModelView.swift +++ b/Conjuguer/Views/ModelView.swift @@ -24,12 +24,34 @@ struct ModelView: View { ScrollView { HStack { VStack(alignment: .leading) { + HStack { + Text(model.exemplarWithPossibleExtraLetters) + .headingLabel() + .frenchPronunciation() + + Text(" (\(model.id))") + .headingLabel() + + Spacer() + } + if let parentId = model.parentId, let parent = VerbModel.models[parentId] { - Text("\(L.ModelView.parent): \(parent.exemplarWithPossibleExtraLetters) (\(parent.id))") - .headingLabel() + HStack { + Text("\(L.ModelView.parent): ") + .headingLabel() + + Text(parent.exemplarWithPossibleExtraLetters) + .headingLabel() + .frenchPronunciation() + + Text(" (\(parent.id))") + .headingLabel() + + Spacer() + } } Text(model.description) @@ -56,12 +78,23 @@ struct ModelView: View { Text(L.ModelView.endings) .subheadingLabel() - Text("\(Tense.participePassé.shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.participeEndingRecursive).font(bodyFont) - Text("\(Tense.indicatifPrésent(.firstSingular).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.indicatifPrésentGroupRecursive.endings(stemAlterations: model.stemAlterations)).font(bodyFont) - Text("\(Tense.impératif(.firstPlural).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.indicatifPrésentGroupRecursive.impératifEndings(stemAlterations: model.stemAlterations)).font(bodyFont) - Text("\(Tense.passéSimple(.firstSingular).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.passéSimpleGroupRecursive.endings).font(bodyFont) - Text("\(Tense.subjonctifPrésent(.firstSingular).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.subjonctifPrésentGroupRecursive.endings(stemAlterations: model.stemAlterations)).font(bodyFont) - Text("\(Tense.subjonctifImparfait(.firstSingular).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.passéSimpleGroupRecursive.subjonctifImparfaitEndings).font(bodyFont) + (Text("\(Tense.participePassé.shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.participeEndingRecursive).font(bodyFont)) + .frenchPronunciation() + + (Text("\(Tense.indicatifPrésent(.firstSingular).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.indicatifPrésentGroupRecursive.endings(stemAlterations: model.stemAlterations)).font(bodyFont)) + .frenchPronunciation() + + (Text("\(Tense.impératif(.firstPlural).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.indicatifPrésentGroupRecursive.impératifEndings(stemAlterations: model.stemAlterations)).font(bodyFont)) + .frenchPronunciation() + + (Text("\(Tense.passéSimple(.firstSingular).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.passéSimpleGroupRecursive.endings).font(bodyFont)) + .frenchPronunciation() + + (Text("\(Tense.subjonctifPrésent(.firstSingular).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.subjonctifPrésentGroupRecursive.endings(stemAlterations: model.stemAlterations)).font(bodyFont)) + .frenchPronunciation() + + (Text("\(Tense.subjonctifImparfait(.firstSingular).shortTitleCaseName): ").font(bodyFont) + Text(mixedCaseString: model.passéSimpleGroupRecursive.subjonctifImparfaitEndings).font(bodyFont)) + .frenchPronunciation() } Spacer() @@ -79,11 +112,13 @@ struct ModelView: View { } .buttonStyle(.borderless) .tint(.customRed) + .accessibility(label: Text(L.Navigation.info)) + .accessibility(hint: Text(L.ModelView.infoButtonHint)) } ForEach(stemAlterations, id: \.self) { alteration in - // TODO: Add an info button describing abbreviations to the right of "Stem Alterations". let appliesToString = Tense.shorthandForNonCompoundTense(appliesTo: alteration.appliesTo) - Text(appliesToString + ": ").font(bodyFont) + Text(mixedCaseString: alteration.toString).font(bodyFont) + (Text(appliesToString + ": ").font(bodyFont) + Text(mixedCaseString: alteration.toString).font(bodyFont)) + .frenchPronunciation() } } @@ -98,12 +133,13 @@ struct ModelView: View { Text(L.ModelView.verbUsing) .subheadingLabel() } - Text(model.verbsWithDeepLinks()).font(bodyFont) + Text(model.verbsWithDeepLinks()) + .font(bodyFont) + .frenchPronunciation() } } } } - .navigationTitle(model.exemplarWithPossibleExtraLetters + " (\(model.id))") .customNavigationBarItems() } .onReceive(Current.$verb) { value in diff --git a/Conjuguer/Views/QuizView.swift b/Conjuguer/Views/QuizView.swift index 8917086..d80168a 100644 --- a/Conjuguer/Views/QuizView.swift +++ b/Conjuguer/Views/QuizView.swift @@ -37,12 +37,14 @@ struct QuizView: View { Group { Text("\(L.QuizView.verbWithColon) \(quiz.questions[quiz.currentQuestionIndex].0.infinitifWithPossibleExtraLetters)") .constrainedBodyLabel() + .frenchPronunciation() Spacer() .frame(height: Layout.defaultSpacing) Text("\(L.QuizView.translationWithColon) \(quiz.questions[quiz.currentQuestionIndex].0.translation)") .constrainedBodyLabel() + .englishPronunciation() } Spacer() @@ -51,12 +53,14 @@ struct QuizView: View { Group { Text("\(L.QuizView.pronounWithColon) \(quiz.questions[quiz.currentQuestionIndex].1.pronounWithGender)") .constrainedBodyLabel() + .frenchPronunciation() Spacer() .frame(height: Layout.defaultSpacing) Text("\(L.QuizView.tenseWithColon) \(quiz.questions[quiz.currentQuestionIndex].1.titleCaseName.lowercased())") .constrainedBodyLabel() + .frenchPronunciation() Spacer() .frame(height: Layout.defaultSpacing) diff --git a/Conjuguer/Views/VerbBrowseView.swift b/Conjuguer/Views/VerbBrowseView.swift index 3f5a531..fb04952 100644 --- a/Conjuguer/Views/VerbBrowseView.swift +++ b/Conjuguer/Views/VerbBrowseView.swift @@ -35,12 +35,11 @@ struct VerbBrowseView: View { NavigationLink(destination: VerbView(verb: verb), label: { ZStack { Color.customBackground - Text(verb.infinitifWithPossibleExtraLetters) - .tableText() } }) - .buttonStyle(PlainButtonStyle()) + .buttonStyle(PlainButtonStyle()) + .frenchPronunciation() } } .navigationBarTitle(L.Navigation.verbs) @@ -50,10 +49,9 @@ struct VerbBrowseView: View { } .navigationViewStyle(.stack) // https://stackoverflow.com/a/66024249 .padding() - .searchable(text: $searchText, suggestions: { + .searchable(text: $searchText, prompt: "", suggestions: { ForEach(searchResults, id: \.self) { result in Text(result.infinitifWithPossibleExtraLetters) - .tableText() .searchCompletion(result.infinitifWithPossibleExtraLetters) } }) diff --git a/Conjuguer/Views/VerbView.swift b/Conjuguer/Views/VerbView.swift index 5ffff5a..86571e6 100644 --- a/Conjuguer/Views/VerbView.swift +++ b/Conjuguer/Views/VerbView.swift @@ -24,22 +24,32 @@ struct VerbView: View { ScrollView { VStack(alignment: .leading) { - if shouldShowVerbHeading { + Group { Text(verb.infinitifWithPossibleExtraLetters) .headingLabel() + .frenchPronunciation() Spacer() - } - - Text(L.VerbView.overview) - .subheadingLabel() - .leftAligned() - Text(verb.translation) - .bodyLabel() + Text(L.VerbView.overview) + .subheadingLabel() + .leftAligned() - if let model = VerbModel.models[verb.model] { - Text("\(L.VerbView.model) \(model.exemplar) (\(model.id))") + Text(verb.translation) .bodyLabel() + .englishPronunciation() + + if let model = VerbModel.models[verb.model] { + HStack { + Text(L.VerbView.modelWithColon + " ") + .bodyLabel() + Text(model.exemplar + " ") + .bodyLabel() + .frenchPronunciation() + Text("(\(model.id))") + .bodyLabel() + Spacer() + } + } } Group { @@ -53,16 +63,17 @@ struct VerbView: View { .bodyLabel() } - if verb.auxiliary == .être { - Text(L.VerbView.auxiliaryÊtre) + HStack { + Text(L.VerbView.auxiliaryWithColon + " ") .bodyLabel() - } else { - Text(L.VerbView.auxiliaryAvoir) + Text(verb.auxiliary.verb) .bodyLabel() + .frenchPronunciation() + Spacer() } if let frequency = verb.frequency { - Text("\(L.VerbView.frequency): \(frequency) / \(FrequencyParser.maxFrequency)") + Text("\(L.VerbView.frequencyWithColon) \(frequency) / \(FrequencyParser.maxFrequency)") .bodyLabel() } } @@ -86,11 +97,13 @@ struct VerbView: View { Text(example) .bodyLabel() + .frenchPronunciation() if let source = verb.source { Text(source) .smallLabel() .rightAligned() + .frenchPronunciation() } } } @@ -101,29 +114,64 @@ struct VerbView: View { Text(L.VerbView.personlessConjugations) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + + ZStack { + Color.customBackground + .accessibility(value: Text(verb.personlessConjugations)) + .frenchPronunciation() - let passéPart = Text(verb: verb, tense: .participePassé).font(bodyFont) + Text(", ").font(bodyFont) - let présentPart = Text(verb: verb, tense: .participePrésent).font(bodyFont) + Text(", ").font(bodyFont) - let futurPart = Text(verb: verb, tense: .radicalFutur).font(bodyFont) + let passéPart = Text(verb: verb, tense: .participePassé).font(bodyFont) + Text(", ").font(bodyFont) + let présentPart = Text(verb: verb, tense: .participePrésent).font(bodyFont) + Text(", ").font(bodyFont) + let futurPart = Text(verb: verb, tense: .radicalFutur).font(bodyFont) - passéPart + présentPart + futurPart + (passéPart + présentPart + futurPart) + .leftAligned() + .accessibilityHidden(true) + } Spacer() Group { Text(Tense.indicatifPrésent(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .indicatifPrésent(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .indicatifPrésent(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .indicatifPrésent(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.passéSimple(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .passéSimple(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .passéSimple(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .passéSimple(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() } @@ -131,24 +179,64 @@ struct VerbView: View { Group { Text(Tense.imparfait(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .imparfait(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .imparfait(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .imparfait(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) + Spacer() Text(Tense.futurSimple(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .futurSimple(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .futurSimple(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .futurSimple(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.conditionnelPrésent(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .conditionnelPrésent(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .conditionnelPrésent(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .conditionnelPrésent(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() } @@ -156,25 +244,64 @@ struct VerbView: View { Group { Text(Tense.subjonctifPrésent(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .subjonctifPrésent(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .subjonctifPrésent(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .subjonctifPrésent(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.subjonctifImparfait(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .subjonctifImparfait(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .subjonctifImparfait(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .subjonctifImparfait(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.impératif(.firstPlural).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.impératifPersonNumbers, id: \.self) { personNumber in - Text(verb: verb, tense: .impératif(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .impératif(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .impératif(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) } } @@ -184,7 +311,8 @@ struct VerbView: View { Text(L.VerbView.showCompoundTenses) .subheadingLabel() } - .toggleStyle(SwitchToggleStyle(tint: .customRed)) + .toggleStyle(SwitchToggleStyle(tint: .customRed)) + .padding(.top, Layout.defaultSpacing) if shouldShowCompoundTenses { Group { @@ -193,25 +321,64 @@ struct VerbView: View { Group { Text(Tense.passéComposé(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + //.padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .passéComposé(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .passéComposé(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .passéComposé(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.plusQueParfait(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .plusQueParfait(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .plusQueParfait(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .plusQueParfait(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.passéAntérieur(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .passéAntérieur(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .passéAntérieur(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .passéAntérieur(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() } @@ -219,25 +386,64 @@ struct VerbView: View { Group { Text(Tense.passéSurcomposé(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .passéSurcomposé(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .passéSurcomposé(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .passéSurcomposé(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.futurAntérieur(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .futurAntérieur(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .futurAntérieur(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .futurAntérieur(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.conditionnelPassé(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .conditionnelPassé(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .conditionnelPassé(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .conditionnelPassé(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() } @@ -245,25 +451,66 @@ struct VerbView: View { Group { Text(Tense.subjonctifPassé(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .subjonctifPassé(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .subjonctifPassé(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .subjonctifPassé(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.subjonctifPlusQueParfait(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.allCases, id: \.self) { personNumber in - Text(verb: verb, tense: .subjonctifPlusQueParfait(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .subjonctifPlusQueParfait(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .subjonctifPlusQueParfait(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) Spacer() Text(Tense.impératifPassé(.firstSingular).titleCaseName) .subheadingLabel() + .frenchPronunciation() + .padding(.bottom, -1.0 * Layout.defaultSpacing) + .padding(.top, Layout.defaultSpacing) + ForEach(PersonNumber.impératifPersonNumbers, id: \.self) { personNumber in - Text(verb: verb, tense: .impératifPassé(personNumber)).font(bodyFont) + ZStack { + Color.customBackground + .accessibility(value: Text(verb: verb, tense: .impératifPassé(personNumber), shouldShowIrregularities: false)) + .frenchPronunciation() + + Text(verb: verb, tense: .impératifPassé(personNumber)).font(bodyFont) + .leftAligned() + .accessibilityHidden(true) + } } + .padding(.bottom, -1.0 * Layout.defaultSpacing) + + Spacer() } } } @@ -272,7 +519,6 @@ struct VerbView: View { Current.analytics.recordViewAppeared("\(VerbView.self)") } } - .navigationTitle(verb.infinitifWithPossibleExtraLetters) .customNavigationBarItems() } }