From 055b578e8f54329eeede4a67872f218500a457f2 Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Wed, 3 Apr 2024 01:33:12 +0200 Subject: [PATCH] #378 keep db manager but remove references to rework + changelog --- CHANGELOG.md | 235 ++-- .../KeyboardViewController.swift | 1101 +++++++++-------- Keyboards/KeyboardsBase/LoadData.swift | 158 +++ .../ScribeFunctionality/Annotate.swift | 61 +- .../ScribeFunctionality/Conjugate.swift | 60 +- .../ScribeFunctionality/Plural.swift | 64 +- .../ScribeFunctionality/Translate.swift | 57 +- Scribe.xcodeproj/project.pbxproj | 26 + 8 files changed, 1098 insertions(+), 664 deletions(-) create mode 100644 Keyboards/KeyboardsBase/LoadData.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cce4beb..9e929b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,33 +14,47 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). ### ✨ New Features -- Adds a new menu to the Scribe app allowing users to set their preferences for their respective language keyboards! +- Adds a new menu to the Scribe app allowing users to set their preferences for their respective language keyboards ([#16](https://github.com/scribe-org/Scribe-iOS/issues/16))! - Users now have easy access to the Scribe GitHub, the Matrix community, rating the app, sending bug reports and emailing the team. - Settings options include: - - Allowing the user to add a command and period to the letter keys. - - Allowing the user to disable emoji autosuggestions and autocompletions. - - Allowing the user to disable accented characters on the letter keys. - - Menu screens can be swiped between via an implementation using SwipeableTabBarController. -- German indefinite pronouns are now selectable from the case-declension display. + - Allowing the user to add a comma and period to the letter keys ([#196](https://github.com/scribe-org/Scribe-iOS/issues/196), [#308](https://github.com/scribe-org/Scribe-iOS/issues/308)). + - Allowing the user to disable emoji autosuggestions and autocompletions ([#310](https://github.com/scribe-org/Scribe-iOS/issues/196), [#311](https://github.com/scribe-org/Scribe-iOS/issues/308)). + - Allowing the user to disable accented characters on the letter keys ([#339](https://github.com/scribe-org/Scribe-iOS/issues/339), [#372](https://github.com/scribe-org/Scribe-iOS/issues/372)). + - Menu screens can be swiped between via an implementation using [SwipeableTabBarController](https://github.com/marcosgriselli/SwipeableTabBarController). + - Menu options have descriptions of their functionality listed beneath them ([#330](https://github.com/scribe-org/Scribe-iOS/issues/339), [#372](https://github.com/scribe-org/Scribe-iOS/issues/330)). +- German indefinite pronouns are now selectable from the case-declension display ([#303](https://github.com/scribe-org/Scribe-iOS/issues/303)). - German imperfect verb conjugations now insert both the auxiliary verb and the past participle with the cursor between them. - Tab and caps lock keys and their functionalities have been added to expanded iPad layouts ([#371](https://github.com/scribe-org/Scribe-iOS/issues/371)). ### 🎨 Design Changes -- iPad keyboards are now more reflective of their system keyboard counterparts for devices above a certain width. +- iPad keyboards are now more reflective of their system keyboard counterparts for devices above a certain width ([#33](https://github.com/scribe-org/Scribe-iOS/issues/33), [#352](https://github.com/scribe-org/Scribe-iOS/issues/352)). +- Vertical spacing between keys on iPads has been decreased so that keys are more square and thus more in line with system keyboards. - + +### ♻️ Code Refactoring -- Bugs were fixed that were causing the autocompletions to trigger to regularly. --> +- Magic numbers for interface radii and other sizing dimensions have been converted to defined variables ([#379](https://github.com/scribe-org/Scribe-iOS/issues/379)). +- Usage of force-unwraps was dramatically reduced in the codebase ([#379](https://github.com/scribe-org/Scribe-iOS/issues/379)). +- Usage of `guard let` and `if let` increased throughout the codebase to assure that early and safe nil-unwrapping ([#379](https://github.com/scribe-org/Scribe-iOS/issues/379)). +- The code for settings keyboard key dimensions and padding was extracted into functions for maintainability ([#383](https://github.com/scribe-org/Scribe-iOS/issues/383)). +- While loops were replaced by for loops in places where they were being used inappropriately ([#380](https://github.com/scribe-org/Scribe-iOS/issues/380)). +- The [Scribe-i18n](https://github.com/scribe-org/Scribe-i18n) directory has been added for future localization work. +- SQLite queries were refactored to extract the DB access logic into a common file ([#378](https://github.com/scribe-org/Scribe-iOS/issues/378)). # Scribe-iOS 2.3.0 ### ✨ New Features -- Noun genders are now displayed to the user under autosuggestions and autocompletions. +- Noun genders are now displayed to the user under autosuggestions and autocompletions ([#164](https://github.com/scribe-org/Scribe-iOS/issues/164)). - The word that the user is typing is available as an autocompletion in cases where pressing space will insert an autocompletion. - Auto completion and suggestion buttons are deactivated if there is no word being displayed. -- Autosuggestions and emoji suggestions are now updated when a user checks the annotation of a word by pressing the Scribe key. +- Autosuggestions and emoji suggestions are now updated when a user checks the annotation of a word by pressing the Scribe key ([#291](https://github.com/scribe-org/Scribe-iOS/issues/291)). - The capitalization of autosuggestions is maintained if the word is capitalized to assure that capitalized nouns are presented properly. - The app screen now includes information about Scribe's relation to Wikimedia as well as license information for code used in development. @@ -64,7 +78,7 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). ### ♻️ Code Refactoring -- All unnecessary explicit boolean checks were removed from the codes in favor of implicit checks. +- All unnecessary explicit boolean checks were removed from the codes in favor of implicit checks ([#289](https://github.com/scribe-org/Scribe-iOS/issues/289)). - The logic of `selectedWordAnnotation` and `typedWordAnnotation` is now shared in a single function. - Many variable names have been changed to be zero indexed. @@ -72,17 +86,17 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). ### ✨ New Features -- Emoji autocompletions and autosuggestions are now available as the user types. +- Emoji autocompletions and autosuggestions are now available as the user types ([#51](https://github.com/scribe-org/Scribe-iOS/issues/51), ([#276](https://github.com/scribe-org/Scribe-iOS/issues/276))). - There are a maximum of two emojis available to select on iPhones and three on iPads. - - The user can also repeat emoji autocompletions and autosuggestions. + - The user can also repeat emoji autocompletions and autosuggestions ([#283](https://github.com/scribe-org/Scribe-iOS/issues/283)). - Emoji autocomplete and autosuggest keywords have also been added as possible autocompletion words. -- Added an action to command bar information icon to explain Wikidata and Scribe's relation to it. -- Added highlight for autocompletion if it is the word typed. -- If a word is the only autosuggestion, hitting the space bar inserts the suggestion. +- Added an action to command bar information icon to explain Wikidata and Scribe's relation to it ([#214](https://github.com/scribe-org/Scribe-iOS/issues/214)). +- Added highlight for autocompletion if it is the word typed ([#250](https://github.com/scribe-org/Scribe-iOS/issues/250)). +- If a word is the only autosuggestion, hitting the space bar inserts the suggestion ([#256](https://github.com/scribe-org/Scribe-iOS/issues/256)). - An undo option is included within autosuggestions if the user does not want the space completion. -- Added Demonstrative pronouns to German preposition declension tables. -- Added contracted preposition annotation to the German keyboard. -- Pressing dash twice now inserts an em dash in the text proxy. +- Added Demonstrative pronouns to German preposition declension tables ([#249](https://github.com/scribe-org/Scribe-iOS/issues/249)). +- Added contracted preposition annotation to the German keyboard ([#279](https://github.com/scribe-org/Scribe-iOS/issues/279)). +- Pressing dash twice now inserts an em dash in the text proxy ([#280](https://github.com/scribe-org/Scribe-iOS/issues/280)). ### 🗃️ Data Added @@ -96,7 +110,7 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). ### 🎨 Design Changes -- The Scribe application receives dark mode in this version. +- The Scribe application receives dark mode in this version ([#260](https://github.com/scribe-org/Scribe-iOS/issues/260)). - The app icon has been made more modern and glossy and the direct shadow has been removed. - Minor adjustments to the original app screen texts and colors have been made. - The resolution of the Scribe key has been improved. @@ -108,11 +122,11 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). - The select keyboard button has been moved to the bottom left most position on iPads. - The gear icon in the top left of the keyboard installation steps is now a more simple version. - iPad application texts were made slightly larger. -- Images and videos for 6.7 inch iPhones have been added to the App Store. +- Images and videos for 6.7 inch iPhones have been added to the App Store ([#225](https://github.com/scribe-org/Scribe-iOS/issues/225)). ### 🌐 Localization -- The Scribe app has been localized into German for users that have it as their system language. +- The Scribe app has been localized into German for users that have it as their system language ([#9](https://github.com/scribe-org/Scribe-iOS/issues/9)). ### 🐞 Bug Fixes @@ -122,31 +136,31 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). ### ⚖️ Legal -- A German version of the privacy policy was added. -- All versions of the privacy policy now note that the English version takes precedence over all others. +- A German version of the privacy policy was added ([#9](https://github.com/scribe-org/Scribe-iOS/issues/9)). +- All versions of the privacy policy now note that the English version takes precedence over all others ([#9](https://github.com/scribe-org/Scribe-iOS/issues/9)). - The text of the privacy policy was updated slightly for readability. - Information about data from Unicode CLDR for emoji suggestions and completions was added to the privacy policy. ### ♻️ Code Refactoring -- Scribe data is now loaded into SQLite database tables to make data reference less memory intensive and mitigate crashes. -- All prior JSON data references have been replaced with database queries and JSON language data files have been removed. -- GRDB.swift was added to the dependencies. +- Scribe data is now loaded into SQLite database tables to make data reference less memory intensive and mitigate crashes ([#96](https://github.com/scribe-org/Scribe-iOS/issues/96)). +- All prior JSON data references have been replaced with database queries and JSON language data files have been removed ([#96](https://github.com/scribe-org/Scribe-iOS/issues/96)). +- [GRDB.swift](https://github.com/groue/GRDB.swift) was added to the dependencies. # Scribe-iOS 2.1.0 ### ⌨️ New Keyboards -- Adds a QWERTY keyboard option for French. +- Adds a QWERTY keyboard option for French ([#229](https://github.com/scribe-org/Scribe-iOS/issues/229)). ### ✨ New Features -- The left and right buttons in the conjugation and declination views are disabled now if pressing them will not lead to a change in the view. -- Autosuggestions for pronouns have been improved for German, French and Spanish. +- The left and right buttons in the conjugation and declination views are disabled now if pressing them will not lead to a change in the view ([#211](https://github.com/scribe-org/Scribe-iOS/issues/211)). +- Autosuggestions for pronouns have been improved for German, French and Spanish ([#208](https://github.com/scribe-org/Scribe-iOS/issues/208)). - The keyboards shift state is disabled by pressing an autocompletion or autosuggestion. -- Autocomplete now functions after quotes, slashes and hashtags. -- Scribe can now access unordered names in the user's contacts to present them as autocompletions. -- The delete button now speeds up as the user holds it. +- Autocomplete now functions after quotes, slashes and hashtags ([#234](https://github.com/scribe-org/Scribe-iOS/issues/234)). +- Scribe can now access unordered names in the user's contacts to present them as autocompletions ([#201](https://github.com/scribe-org/Scribe-iOS/issues/201)). +- The delete button now speeds up as the user holds it ([#147](https://github.com/scribe-org/Scribe-iOS/issues/147)). - Typing a period, comma, question mark or exclamation point now removes a space before them if there is one. ### 🗃️ Data Added @@ -164,8 +178,8 @@ Thousands of new French verb conjugations have been added! ### 🎨 Design Changes - The labels for conjugations and declinations have been made darker in dark mode to be more readable. -- French keyboards are now named based on their keyboard style. -- Keyboards will now display the language followed by "(Scribe)" for a second before showing the language's word for space, similar to the system keyboards. +- French keyboards are now named based on their keyboard style ([#229](https://github.com/scribe-org/Scribe-iOS/issues/229)). +- Keyboards will now display the language followed by "(Scribe)" for a second before showing the language's word for space, similar to the system keyboards ([#34](https://github.com/scribe-org/Scribe-iOS/issues/34)). ### 🐞 Bug Fixes @@ -174,8 +188,8 @@ Thousands of new French verb conjugations have been added! ### ♻️ Code Refactoring -- Loading JSONs for language data is now handled by SwiftyJSON, with the code being refactored to implement it. - - This is a first step in refining the data loading process to better handle large amounts of data. +- Loading JSONs for language data is now handled by [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON), with the code being refactored to implement it ([#231](https://github.com/scribe-org/Scribe-iOS/issues/231)). + - This is a first step in refining the data loading process to better handle large amounts of data ([#96](https://github.com/scribe-org/Scribe-iOS/issues/96)). - Light and dark mode colors are now defined in `Assets.xcassets` and accessed via `ScribeColor.getter:color` or `UIColor`'s new convenience initializer. - Variants of the Scribe key icon are placed into `Assets.xcassets`, making it unnecessary to check for light/dark mode and device type in code. @@ -183,10 +197,10 @@ Thousands of new French verb conjugations have been added! ### ✨ New Features -- Scribe now includes a baseline Wikidata and Wikipedia based autocomplete feature. +- Scribe now includes a baseline Wikidata and Wikipedia based autocomplete feature ([#188](https://github.com/scribe-org/Scribe-iOS/issues/188)). - Suggestions include the next possible noun as well as the most common words in the keyboard language. -- Scribe now includes a baseline autosuggest feature that suggests words derived from Wikipedia that most often follow a given word. -- Preposition annotations can now be clicked to display a case pronoun display from which pronouns can be selected. +- Scribe now includes a baseline autosuggest feature that suggests words derived from Wikipedia that most often follow a given word ([#194](https://github.com/scribe-org/Scribe-iOS/issues/194)). +- Preposition annotations can now be clicked to display a case pronoun display from which pronouns can be selected ([#210](https://github.com/scribe-org/Scribe-iOS/issues/210)). - Users are able to select from the display based on subjects and objects to exactly specify which pronoun they need. ### 🗃️ Data Added @@ -201,16 +215,15 @@ Thousands of new French verb conjugations have been added! ### 🎨 Design Changes -- Noun and preposition annotation has been updated to not show the word being annotated. +- Noun and preposition annotation has been updated to not show the word being annotated ([#188](https://github.com/scribe-org/Scribe-iOS/issues/188)). - This saves space above the keyboard for autocomplete and autosuggest. - The annotation colors have been changed to match the new backgrounds. - The delete key features a pressed state style similar to the native keyboard. -- New layouts for pronoun declination have been added to the keyboards. +- New layouts for pronoun declination have been added to the keyboards ([#210](https://github.com/scribe-org/Scribe-iOS/issues/210)). - The message indicating that the word isn't in Wikidata now comes with an information icon (action pending). -- The App Store images have been updated to reflect the new Wikipedia based autosuggest. -- The App Store videos have been updated to reflect the changes for the new version. -- iPhone 6.7" images and videos have been added to the App Store. -- Other minor changes to images for the App Store. +- The App Store images have been updated to reflect the new Wikipedia based autosuggest ([#199](https://github.com/scribe-org/Scribe-iOS/issues/199)). +- The App Store videos have been updated to reflect the changes for the new version ([#199](https://github.com/scribe-org/Scribe-iOS/issues/199)). +- Other minor changes to images for the App Store ([#199](https://github.com/scribe-org/Scribe-iOS/issues/199)). ### 🌐 Localization @@ -218,11 +231,11 @@ Thousands of new French verb conjugations have been added! ### ⚖️ Legal -- The privacy policy was updated to add information about the Wikipedia text data terms of use. +- The privacy policy was updated to add information about the Wikipedia text data terms of use ([#194](https://github.com/scribe-org/Scribe-iOS/issues/194)). ### ♻️ Code Refactoring -- Boolean states for commands were converted into a single enum to make keyboard states much simpler to work with. +- Boolean states for commands were converted into a single enum to make keyboard states much simpler to work with ([#200](https://github.com/scribe-org/Scribe-iOS/issues/200)). - Code was refactored to work with the new enum style of command state management. - Enums are now used to control switching between conjugations. - Enums are now used to control switching between different conjugation displays. @@ -231,8 +244,8 @@ Thousands of new French verb conjugations have been added! ### ✨ New Features -- Commands now include a greyed out prompt that tells the user to enter a specific word type. -- The return key now changes its icon during commands to make it more apparent as the execution input. +- Commands now include a greyed out prompt that tells the user to enter a specific word type ([#35](https://github.com/scribe-org/Scribe-iOS/issues/35)). +- The return key now changes its icon during commands to make it more apparent as the execution input ([#165](https://github.com/scribe-org/Scribe-iOS/issues/165)). - The link to GitHub in the app now goes to the iOS repo instead of the organization. ### 🗃️ Data Added @@ -272,7 +285,7 @@ Scribe's second design sprint with Spencer Arney focussed on the App Store media ### 🐞 Bug Fixes -- Verb conjugation tables now always return to their base conjugation each time the command is used. +- Verb conjugation tables now always return to their base conjugation each time the command is used ([#168](https://github.com/scribe-org/Scribe-iOS/issues/168)). # Scribe-iOS 1.3.7 @@ -324,14 +337,14 @@ Scribe's second design sprint with Spencer Arney focussed on the App Store media ### ♻️ Code Refactoring -- The data files have been moved to a new directory within the organization on GitHub. +- The data files have been moved to a new directory within the organization on GitHub - [Scribe-Data](https://github.com/scribe-org/Scribe-Data). # Scribe-iOS 1.3.4 ### 🎨 Design Changes - Captions for App Store images have been updated. -- The App Store description has been updated with a reference to Wikidata and grammar improvements. +- The App Store description has been updated with a reference to Wikidata and grammar improvements ([#151](https://github.com/scribe-org/Scribe-iOS/issues/151)). - The open-source images in the App Store has been updated to reference open data and Wikidata. ### ⚖️ Legal @@ -362,7 +375,7 @@ Scribe's second design sprint with Spencer Arney focussed on the App Store media ### 🎨 Design Changes -- Key alternate views appear more quickly. +- Key alternate views appear more quickly ([#145](https://github.com/scribe-org/Scribe-iOS/issues/145)). ### 🐞 Bug Fixes @@ -382,15 +395,15 @@ Scribe's second design sprint with Spencer Arney focussed on the App Store media ### 🐞 Bug Fixes -- The alternate characters of apostrophes and quotation marks have been fixed. -- Key alternate views now stay if the key is canceled as they were disappearing too easily. +- The alternate characters of apostrophes and quotation marks have been fixed ([#142](https://github.com/scribe-org/Scribe-iOS/issues/142)). +- Key alternate views now stay if the key is canceled as they were disappearing too easily ([#143](https://github.com/scribe-org/Scribe-iOS/issues/143)). - The width of alternate character callouts for certain keys has been fixed for iPhones. # Scribe-iOS 1.3.0 ### ⌨️ New Keyboards -- Adds an Italian keyboard. +- Adds an Italian keyboard ([#132](https://github.com/scribe-org/Scribe-iOS/issues/132)). ### 🗃️ Data Added @@ -398,20 +411,20 @@ Scribe's second design sprint with Spencer Arney focussed on the App Store media ### 🎨 Design Changes -- The messages that tell the user a noun is already plural have been translated to the keyboard's language. +- The messages that tell the user a noun is already plural have been translated to the keyboard's language ([#138](https://github.com/scribe-org/Scribe-iOS/issues/138)). - The keyboard height has been increased for landscape mode on iPads. -- Characters on keys have been made larger so they reflect the system keyboards better. -- All letter, number and special keys now pop up after being pressed. -- Hold to select characters have been redesigned to reflect the addition of keys popping up. -- All App Store media has been redone to reflect these changes. +- Characters on keys have been made larger so they reflect the system keyboards better ([#131](https://github.com/scribe-org/Scribe-iOS/issues/131)). +- All letter, number and special keys now pop up after being pressed ([#26](https://github.com/scribe-org/Scribe-iOS/issues/26)). +- Hold to select characters have been redesigned to reflect the addition of keys popping up ([#26](https://github.com/scribe-org/Scribe-iOS/issues/26)). +- All App Store media has been redone to reflect these changes ([#139](https://github.com/scribe-org/Scribe-iOS/issues/139)). # Scribe-iOS 1.2.1 ### ✨ New Features -- The keyboard switches back to letter keys after all appropriate symbols followed by space. -- Scribe commands now accept inputs that are followed by a space in case the user accidentally added one. -- Users can now translate pronouns as these were not originally included. +- The keyboard switches back to letter keys after all appropriate symbols followed by space ([#117](https://github.com/scribe-org/Scribe-iOS/issues/117)). +- Scribe commands now accept inputs that are followed by a space in case the user accidentally added one ([#118](https://github.com/scribe-org/Scribe-iOS/issues/118)). +- Users can now translate pronouns as these were not originally included ([#128](https://github.com/scribe-org/Scribe-iOS/issues/128)). ### 🗃️ Data Added @@ -430,19 +443,19 @@ Scribe's second design sprint with Spencer Arney focussed on the App Store media ### 🐞 Bug Fixes -- The Scribe key now switches its icon color with the rest of the keyboard when the user changes color modes. -- Annotations are no longer triggered if a user presses space during a command. +- The Scribe key now switches its icon color with the rest of the keyboard when the user changes color modes ([#116](https://github.com/scribe-org/Scribe-iOS/issues/116)). +- Annotations are no longer triggered if a user presses space during a command ([#123](https://github.com/scribe-org/Scribe-iOS/issues/123)). ### ♻️ Code Refactoring -- Commands buttons are now called keys and the preview bar has been renamed the command bar. -- Force casts are used as little as possible. -- All lines have been reduced to a reasonable length (120 characters) where able. -- All functions have been reduced to a reasonable length (40 lines) where able. -- All functions have been reduced to a reasonable cyclomatic complexity (10 or less) where able. -- All files have been reduced to a reasonable length (400 lines) where able. -- All type bodies have been reduced to a reasonable length (200 lines) where able. -- Scribe has been modularized to be more easily worked with. +- Commands buttons are now called keys and the preview bar has been renamed the command bar ([#1](https://github.com/scribe-org/Scribe-iOS/issues/1)). +- Force casts are used as little as possible ([#1](https://github.com/scribe-org/Scribe-iOS/issues/1)). +- All lines have been reduced to a reasonable length (120 characters) where able ([#1](https://github.com/scribe-org/Scribe-iOS/issues/1)). +- All functions have been reduced to a reasonable length (40 lines) where able ([#1](https://github.com/scribe-org/Scribe-iOS/issues/1)). +- All functions have been reduced to a reasonable cyclomatic complexity (10 or less) where able ([#1](https://github.com/scribe-org/Scribe-iOS/issues/1)). +- All files have been reduced to a reasonable length (400 lines) where able ([#1](https://github.com/scribe-org/Scribe-iOS/issues/1)). +- All type bodies have been reduced to a reasonable length (200 lines) where able ([#1](https://github.com/scribe-org/Scribe-iOS/issues/1)). +- Scribe has been modularized to be more easily worked with ([#1](https://github.com/scribe-org/Scribe-iOS/issues/1)). - The app screen's text was moved to a new directory where localizations will be stored. # Scribe-iOS 1.2.0 @@ -453,35 +466,35 @@ The entire layout of Scribe has been reworked to make the experience more aesthe ### ✨ New Features -- Users can now get to Settings by clicking the installation steps in the app screen. +- Users can now get to Settings by clicking the installation steps in the app screen ([#27](https://github.com/scribe-org/Scribe-iOS/issues/27)). - All keyboards now switch to an English keyboard for translation, with this being in preparation for when more languages can be translated from. ### 🎨 Design Changes -- The logo and icon for Scribe have been reworked to give the app a distinct style. -- The app screen has been completely redone to be more appealing. -- Keyboard layouts, colors and characters have been changed to match system keyboards. -- Translation prompts were changed to be two digit abbreviations of source and target language. -- Colors for noun annotation were updated to improve readability. -- Noun annotation is now done with a square symbol to represent the gender. -- Preposition annotation is now done with a rectangular symbol to represent the case. -- Preposition case abbreviations have been changed to match the language of the keyboard. -- All App Store media has been redone to reflect these changes. +- The logo and icon for Scribe have been reworked to give the app a distinct style ([#24](https://github.com/scribe-org/Scribe-iOS/issues/24)). +- The app screen has been completely redone to be more appealing ([#28](https://github.com/scribe-org/Scribe-iOS/issues/28)). +- Keyboard layouts, colors and characters have been changed to match system keyboards ([#32](https://github.com/scribe-org/Scribe-iOS/issues/32)). +- Translation prompts were changed to be two digit abbreviations of source and target language ([#112](https://github.com/scribe-org/Scribe-iOS/issues/112)). +- Colors for noun annotation were updated to improve readability ([#31](https://github.com/scribe-org/Scribe-iOS/issues/31)). +- Noun annotation is now done with a square symbol to represent the gender ([#112](https://github.com/scribe-org/Scribe-iOS/issues/112)). +- Preposition annotation is now done with a rectangular symbol to represent the case ([#92](https://github.com/scribe-org/Scribe-iOS/issues/92)). +- Preposition case abbreviations have been changed to match the language of the keyboard ([#92](https://github.com/scribe-org/Scribe-iOS/issues/92)). +- All App Store media has been redone to reflect these changes ([#114](https://github.com/scribe-org/Scribe-iOS/issues/114)). ### 🐞 Bug Fixes -- The keyboard colors should not switch randomly between light and dark mode now. +- The keyboard colors should not switch randomly between light and dark mode now ([#112](https://github.com/scribe-org/Scribe-iOS/issues/112)). - Removed an additional character from the Spanish iPad keyboard's special keys. ### ⚖️ Legal -- The privacy policy was updated to reflect the addition of the GitHub, Inc icon into the app. +- The privacy policy was updated to reflect the addition of the GitHub, Inc icon into the app ([#50](https://github.com/scribe-org/Scribe-iOS/issues/50)). # Scribe-iOS 1.1.1 ### 🗃️ Data Added -Data updates are now all done through a single Python file - update_data.py. +Data updates are now all done through a single Python file - update_data.py ([#95](https://github.com/scribe-org/Scribe-iOS/issues/95)). - French: 11 nouns - German: 152 nouns, 1 verb, 1 preposition @@ -493,12 +506,12 @@ Data updates are now all done through a single Python file - update_data.py. - The text size for the command bar in landscape mode for phones was made smaller. - The height of the keyboard in landscape mode for phones was made slightly smaller. -- App store images were updated to combine the dark mode and devices screens. +- App store images were updated to combine the dark mode and devices screens ([#98](https://github.com/scribe-org/Scribe-iOS/issues/98)). ### 🐞 Bug Fixes -- The keyboard colors now update if the user switches between light and dark mode. -- Auto-capitalization and switching to the letter keys weren't always triggered after a period. +- The keyboard colors now update if the user switches between light and dark mode ([#31](https://github.com/scribe-org/Scribe-iOS/issues/31)). +- Auto-capitalization and switching to the letter keys weren't always triggered after a period ([#97](https://github.com/scribe-org/Scribe-iOS/issues/97)). - Shifting orientation from portrait to landscape is now seamless, but landscape to portrait is still a WIP. ### ♻️ Code Refactoring @@ -510,14 +523,14 @@ Data updates are now all done through a single Python file - update_data.py. ### ⌨️ New Keyboards -- Adds Russian, French, Portuguese and Swedish keyboards. +- Adds Russian ([#6](https://github.com/scribe-org/Scribe-iOS/issues/6)), French ([#68](https://github.com/scribe-org/Scribe-iOS/issues/68)), Portuguese ([#67](https://github.com/scribe-org/Scribe-iOS/issues/67)) and Swedish keyboards ([#78](https://github.com/scribe-org/Scribe-iOS/issues/78)). ### ✨ New Features -- Hold-to-select functionality for symbol keys. +- Hold-to-select functionality for symbol keys ([#69](https://github.com/scribe-org/Scribe-iOS/issues/69)). - The keyboard keys are capitalized if the user deletes at the start of the command bar. -- Removes noun-gender annotation for given names to avoid misgendering people. -- Users are now able to pass upper-case arguments to translate and conjugate. +- Removes noun-gender annotation for given names to avoid misgendering people ([#90](https://github.com/scribe-org/Scribe-iOS/issues/90)). +- Users are now able to pass upper-case arguments to translate and conjugate ([#93](https://github.com/scribe-org/Scribe-iOS/issues/93)). ### 🗃️ Data Added @@ -531,18 +544,18 @@ Data updates are now all done through a single Python file - update_data.py. ### 🎨 Design Changes - Improves the display of the caps lock key by making its background the key pressed color. -- Updates the App Store images and videos. -- Scribe command titles are now in the keyboard language for a more immersive experience. +- Updates the App Store images and videos ([#84](https://github.com/scribe-org/Scribe-iOS/issues/84)). +- Scribe command titles are now in the keyboard language for a more immersive experience ([#91](https://github.com/scribe-org/Scribe-iOS/issues/91)). - Translate for Russian switches to an English keyboard. ### 🐞 Bug Fixes - German keyboards had the dollar sign shown on the number keys instead of the euro sign. - iPads had a semicolon key that also had apostrophes. -- Hold-to-select keys wouldn't return to their original color. -- The keyboard wouldn't always be letter keys when switched to. -- The double space period shortcut wasn't possible after certain special characters and numbers. -- More than one singular gender wasn't being assigned to German nouns in the formatting process. +- Hold-to-select keys wouldn't return to their original color ([#74](https://github.com/scribe-org/Scribe-iOS/issues/74)). +- The keyboard wouldn't always be letter keys when switched to ([#80](https://github.com/scribe-org/Scribe-iOS/issues/80)). +- The double space period shortcut wasn't possible after certain special characters and numbers ([#71](https://github.com/scribe-org/Scribe-iOS/issues/71)). +- More than one singular gender wasn't being assigned to German nouns in the formatting process ([#76](https://github.com/scribe-org/Scribe-iOS/issues/76)). ### ♻️ Code Refactoring @@ -554,25 +567,25 @@ Data updates are now all done through a single Python file - update_data.py. ### ✨ New Features -- Comma-space to letter keys functionality. -- Question mark and exclamation point followed by space to capital letter keys functionality. +- Comma-space to letter keys functionality ([#56](https://github.com/scribe-org/Scribe-iOS/issues/56)). +- Question mark and exclamation point followed by space to capital letter keys functionality ([#59](https://github.com/scribe-org/Scribe-iOS/issues/59)). ### 🎨 Design Changes -- Fixes the display of the system header in the app when the user is in dark mode, as the white text was hard to read. +- Fixes the display of the system header in the app when the user is in dark mode, as the white text was hard to read ([#57](https://github.com/scribe-org/Scribe-iOS/issues/57)). - Fixes the display of the scroll bar in the app when the user is in dark mode, as the white bar wasn't visually appealing. -- The keyboard has been made taller for iPhones to make the buttons larger vertically. -- More space has been added around the buttons to make them better resemble system keyboard spacing. +- The keyboard has been made taller for iPhones to make the buttons larger vertically ([#63](https://github.com/scribe-org/Scribe-iOS/issues/63)). +- More space has been added around the buttons to make them better resemble system keyboard spacing ([#63](https://github.com/scribe-org/Scribe-iOS/issues/63)). ### 🐞 Bug Fixes -- The select keyboard button wouldn't be able to be long held after an initial button is pressed. -- Canceling a command would cause the command bar to read "Not in directory" on a subsequent command. -- The double space period shortcut was being triggered without intent. +- The select keyboard button wouldn't be able to be long held after an initial button is pressed ([#4](https://github.com/scribe-org/Scribe-iOS/issues/4)). +- Canceling a command would cause the command bar to read "Not in directory" on a subsequent command ([#58](https://github.com/scribe-org/Scribe-iOS/issues/58)). +- The double space period shortcut was being triggered without intent ([#55](https://github.com/scribe-org/Scribe-iOS/issues/55)). ### ♻️ Code Refactoring -- The hold-to-select character functions are now combined into one. +- The hold-to-select character functions are now combined into one ([#5](https://github.com/scribe-org/Scribe-iOS/issues/5)). # Scribe-iOS 1.0.0 diff --git a/Keyboards/KeyboardsBase/KeyboardViewController.swift b/Keyboards/KeyboardsBase/KeyboardViewController.swift index 3b1c2484..72dad3fb 100644 --- a/Keyboards/KeyboardsBase/KeyboardViewController.swift +++ b/Keyboards/KeyboardsBase/KeyboardViewController.swift @@ -1,8 +1,21 @@ -// -// KeyboardViewController.swift -// -// Classes for the parent keyboard view controller that language keyboards inherit and keyboard keys. -// +/** + * Classes for the parent keyboard view controller that language keyboards. + * + * Copyright (C) 2023 Scribe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import GRDB import UIKit @@ -23,26 +36,38 @@ class KeyboardViewController: UIInputViewController { /// Changes the height of `stackViewNum` depending on device type and size. func conditionallyShowTopNumbersRow() { if DeviceType.isPhone { - view.addConstraint( - NSLayoutConstraint( - item: stackViewNum!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 0 + if let stackViewNum = stackViewNum { + view.addConstraint( + NSLayoutConstraint( + item: stackViewNum, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 0 + ) ) - ) + } } else if DeviceType.isPad { // Update the size of the numbers row to add it to the view. if usingExpandedKeyboard { - let numbersRowHeight = scribeKey.frame.height - view.addConstraint( - NSLayoutConstraint( - item: stackViewNum!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: numbersRowHeight + let numbersRowHeight = scribeKey.frame.height * 1.8 + if let stackViewNum = stackViewNum { + view.addConstraint( + NSLayoutConstraint( + item: stackViewNum, + attribute: .height, + relatedBy: .equal, + toItem: nil, + attribute: .height, + multiplier: 1, + constant: numbersRowHeight + ) ) - ) + } } else { - view.addConstraint( - NSLayoutConstraint( - item: stackViewNum!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 0 + if let stackViewNum = stackViewNum { + view.addConstraint( + NSLayoutConstraint( + item: stackViewNum, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 0 + ) ) - ) + } } } } @@ -126,15 +151,28 @@ class KeyboardViewController: UIInputViewController { keyboardHeight = 270 } } else if DeviceType.isPad { - if isLandscapeView { - keyboardHeight = 420 + // Expanded keyboard on larger iPads can be higher. + if UIScreen.main.bounds.width > 768 { + if isLandscapeView { + keyboardHeight = 430 + } else { + keyboardHeight = 360 + } } else { - keyboardHeight = 340 + if isLandscapeView { + keyboardHeight = 420 + } else { + keyboardHeight = 340 + } } } + guard let view = view else { + fatalError("The view is nil.") + } + let heightConstraint = NSLayoutConstraint( - item: view!, + item: view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, @@ -193,10 +231,15 @@ class KeyboardViewController: UIInputViewController { /// - A call to loadKeys to reload the display after an orientation change override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) - updateViewConstraints() - isFirstKeyboardLoad = true - loadKeys() - isFirstKeyboardLoad = false + coordinator.animate(alongsideTransition: { _ in + self.updateViewConstraints() + self.loadKeys() + }) + Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in + isFirstKeyboardLoad = true + self.loadKeys() + isFirstKeyboardLoad = false + } } /// Overrides the previous color variables if the user switches between light and dark mode. @@ -209,9 +252,11 @@ class KeyboardViewController: UIInputViewController { alternatesShapeLayer.removeFromSuperlayer() } annotationState = false - isFirstKeyboardLoad = true - loadKeys() - isFirstKeyboardLoad = false + Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in + isFirstKeyboardLoad = true + self.loadKeys() + isFirstKeyboardLoad = false + } } // MARK: Scribe Command Elements @@ -322,11 +367,16 @@ class KeyboardViewController: UIInputViewController { /// - Parameters /// - word: the word for which corresponding emojis should be shown for. func getEmojiAutoSuggestions(for word: String) { - let emojisToDisplay = LanguageDBManager.shared.queryEmojis(of: word) - if emojisToDisplay[0] != "" { + let query = "SELECT * FROM emoji_keywords WHERE word = ?" + let args = [word.lowercased()] + let outputCols = ["emoji_0", "emoji_1", "emoji_2"] + let emojisToDisplay = queryDBRow(query: query, outputCols: outputCols, args: args) + + if !emojisToDisplay[0].isEmpty { emojisToDisplayArray = [String]() currentEmojiTriggerWord = word.lowercased() - if emojisToDisplay[2] != "" && DeviceType.isPad { + + if !emojisToDisplay[2].isEmpty && DeviceType.isPad { for i in 0 ..< 3 { emojisToDisplayArray.append(emojisToDisplay[i]) } @@ -341,7 +391,7 @@ class KeyboardViewController: UIInputViewController { padEmojiDivider1.backgroundColor = UIColor(cgColor: commandBarBorderColor) } conditionallyHideEmojiDividers() - } else if emojisToDisplay[1] != "" { + } else if !emojisToDisplay[1].isEmpty { for i in 0 ..< 2 { emojisToDisplayArray.append(emojisToDisplay[i]) } @@ -365,7 +415,7 @@ class KeyboardViewController: UIInputViewController { /// Generates an array of the three autocomplete words. func getAutocompletions() { completionWords = [" ", " ", " "] - if proxy.documentContextBeforeInput?.count != 0 { + if let documentContext = proxy.documentContextBeforeInput, !documentContext.isEmpty { if let inString = proxy.documentContextBeforeInput { // To only focus on the current word as prefix in autocomplete. currentPrefix = inString.replacingOccurrences(of: pastStringInTextProxy, with: "") @@ -392,16 +442,17 @@ class KeyboardViewController: UIInputViewController { // Trigger autocompletions for selected text instead. if proxy.selectedText != nil && [.idle, .selectCommand, .alreadyPlural, .invalid].contains(commandState) { - currentPrefix = proxy.selectedText! + if let selectedText = proxy.selectedText { + currentPrefix = selectedText + } } // Get options for completion that start with the current prefix and are not just one letter. - let completionOptions = LanguageDBManager.shared.queryAutocompletions(word: currentPrefix) + let completionOptions = queryAutocompletions(word: currentPrefix) - if completionOptions[0] != "" { - var i = 0 + if !completionOptions[0].isEmpty { if completionOptions.count <= 3 { - while i < completionOptions.count { + for i in 0 ..< completionOptions.count { if shiftButtonState == .shift { completionWords[i] = completionOptions[i].capitalize() } else if capsLockButtonState == .locked { @@ -415,10 +466,9 @@ class KeyboardViewController: UIInputViewController { } else { completionWords[i] = completionOptions[i] } - i += 1 } } else { - while i < 3 { + for i in 0 ..< 3 { if shiftButtonState == .shift { completionWords[i] = completionOptions[i].capitalize() } else if capsLockButtonState == .locked { @@ -432,7 +482,6 @@ class KeyboardViewController: UIInputViewController { } else { completionWords[i] = completionOptions[i] } - i += 1 } } } @@ -456,38 +505,39 @@ class KeyboardViewController: UIInputViewController { let prefix = proxy.documentContextBeforeInput?.components(separatedBy: " ").secondToLast() ?? "" completionWords = [String]() - var i = 0 - while i < 3 { + let query = "SELECT * FROM verbs WHERE verb = ?" + for i in 0 ..< 3 { // Get conjugations of the preselected verbs. - let outputCols = [pronounAutosuggestionTenses[prefix.lowercased()]!] - var suggestion = LanguageDBManager.shared.queryVerb(of: verbsAfterPronounsArray[i], with: outputCols)[0] - if suggestion == "" { - suggestion = verbsAfterPronounsArray[i] - } + let args = [verbsAfterPronounsArray[i]] + if let tense = pronounAutosuggestionTenses[prefix.lowercased()] { + let outputCols = [tense] + var suggestion = queryDBRow(query: query, outputCols: outputCols, args: args)[0] + if suggestion == "" { + suggestion = verbsAfterPronounsArray[i] + } - if suggestion == "REFLEXIVE_PRONOUN" && controllerLanguage == "Spanish" { - suggestion = getESReflexivePronoun(pronoun: prefix.lowercased()) - } - if shiftButtonState == .shift { - completionWords.append(suggestion.capitalize()) - } else if capsLockButtonState == .locked { - completionWords.append(suggestion.uppercased()) - } else { - completionWords.append(suggestion) + if suggestion == "REFLEXIVE_PRONOUN" && controllerLanguage == "Spanish" { + suggestion = getESReflexivePronoun(pronoun: prefix.lowercased()) + } + if shiftButtonState == .shift { + completionWords.append(suggestion.capitalize()) + } else if capsLockButtonState == .locked { + completionWords.append(suggestion.uppercased()) + } else { + completionWords.append(suggestion) + } } - i += 1 } } /// Generates an array of three words that serve as baseline autosuggestions. func getDefaultAutosuggestions() { - var i = 0 completionWords = [String]() - if allowUndo { - completionWords.append(previousWord) - i += 1 - } - while i < 3 { + for i in 0 ..< 3 { + if allowUndo { + completionWords.append(previousWord) + continue + } if shiftButtonState == .shift { completionWords.append(baseAutosuggestions[i].capitalize()) } else if capsLockButtonState == .locked { @@ -495,7 +545,6 @@ class KeyboardViewController: UIInputViewController { } else { completionWords.append(baseAutosuggestions[i]) } - i += 1 } } @@ -518,7 +567,9 @@ class KeyboardViewController: UIInputViewController { // Trigger autocompletions for selected text instead. if proxy.selectedText != nil && [.idle, .selectCommand, .alreadyPlural, .invalid].contains(commandState) { - prefix = proxy.selectedText! + if let selectedText = proxy.selectedText { + prefix = selectedText + } } if prefix.isNumeric { @@ -528,40 +579,45 @@ class KeyboardViewController: UIInputViewController { } else { // We have to consider these different cases as the key always has to match. // Else, even if the lowercased prefix is present in the dictionary, if the actual prefix isn't present we won't get an output. - - let suggestionsLowerCasePrefix = LanguageDBManager.shared.queryAutosuggestions(of: prefix) - let suggestionsCapitalizedPrefix = LanguageDBManager.shared.queryAutosuggestions(of: prefix) - if suggestionsLowerCasePrefix[0] != "" { + let query = "SELECT * FROM autosuggestions WHERE word = ?" + let argsLower = [prefix.lowercased()] + let argsCapitalize = [prefix.capitalized] + let outputCols = ["suggestion_0", "suggestion_1", "suggestion_2"] + + let suggestionsLowerCasePrefix = queryDBRow(query: query, outputCols: outputCols, args: argsLower) + let suggestionsCapitalizedPrefix = queryDBRow(query: query, outputCols: outputCols, args: argsCapitalize) + if !suggestionsLowerCasePrefix[0].isEmpty { completionWords = [String]() - var i = 0 - if allowUndo { - completionWords.append(previousWord) - i += 1 - } - while i < 3 { + for i in 0 ..< 3 { + if allowUndo { + completionWords.append(previousWord) + continue + } if shiftButtonState == .shift { completionWords.append(suggestionsLowerCasePrefix[i].capitalize()) } else if capsLockButtonState == .locked { completionWords.append(suggestionsLowerCasePrefix[i].uppercased()) } else { - let nounForm = LanguageDBManager.shared.queryNounForm(of: suggestionsLowerCasePrefix[i])[0] - hasNounForm = nounForm != "" + let nounGenderQuery = "SELECT * FROM nouns WHERE noun = ?" + let nounGenderArgs = [suggestionsLowerCasePrefix[i]] + let outputCols = ["form"] + + let nounForm = queryDBRow(query: nounGenderQuery, outputCols: outputCols, args: nounGenderArgs)[0] + hasNounForm = !nounForm.isEmpty if !hasNounForm { completionWords.append(suggestionsLowerCasePrefix[i].lowercased()) } else { completionWords.append(suggestionsLowerCasePrefix[i]) } } - i += 1 } - } else if suggestionsCapitalizedPrefix[0] != "" { + } else if !suggestionsCapitalizedPrefix[0].isEmpty { completionWords = [String]() - var i = 0 - if allowUndo { - completionWords.append(previousWord) - i += 1 - } - while i < 3 { + for i in 0 ..< 3 { + if allowUndo { + completionWords.append(previousWord) + continue + } if shiftButtonState == .shift { completionWords.append(suggestionsCapitalizedPrefix[i].capitalize()) } else if capsLockButtonState == .locked { @@ -569,7 +625,6 @@ class KeyboardViewController: UIInputViewController { } else { completionWords.append(suggestionsCapitalizedPrefix[i]) } - i += 1 } } else { getDefaultAutosuggestions() @@ -695,9 +750,9 @@ class KeyboardViewController: UIInputViewController { radius: commandKeyCornerRadius ) if DeviceType.isPhone { - pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.435) + pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPhone) } else if DeviceType.isPad { - pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) + pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPad) } activateBtn(btn: pluralKey) @@ -721,11 +776,11 @@ class KeyboardViewController: UIInputViewController { styleBtn(btn: phoneEmojiKey1, title: emojisToDisplayArray[1], radius: commandKeyCornerRadius) if DeviceType.isPhone { - phoneEmojiKey0.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.435) - phoneEmojiKey1.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.435) + phoneEmojiKey0.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPhone) + phoneEmojiKey1.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPhone) } else if DeviceType.isPad { - phoneEmojiKey0.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) - phoneEmojiKey1.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) + phoneEmojiKey0.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPad) + phoneEmojiKey1.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPad) } activateBtn(btn: phoneEmojiKey0) @@ -740,9 +795,9 @@ class KeyboardViewController: UIInputViewController { styleBtn(btn: padEmojiKey1, title: emojisToDisplayArray[1], radius: commandKeyCornerRadius) styleBtn(btn: padEmojiKey2, title: emojisToDisplayArray[2], radius: commandKeyCornerRadius) - padEmojiKey0.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) - padEmojiKey1.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) - padEmojiKey2.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) + padEmojiKey0.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarEmojiKeyFont) + padEmojiKey1.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarEmojiKeyFont) + padEmojiKey2.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarEmojiKeyFont) activateBtn(btn: padEmojiKey0) activateBtn(btn: padEmojiKey1) @@ -765,12 +820,17 @@ class KeyboardViewController: UIInputViewController { /// Note: the completion is appended after the typed text if this is not ran. func clearPrefixFromTextFieldProxy() { // Only delete characters for autocomplete, not autosuggest. - if currentPrefix != "" && autoActionState != .suggest { - if proxy.documentContextBeforeInput?.count != 0 { - for _ in 0 ..< currentPrefix.count { - proxy.deleteBackward() - } - } + guard !currentPrefix.isEmpty, autoActionState != .suggest else { + return + } + + guard let documentContext = proxy.documentContextBeforeInput, !documentContext.isEmpty else { + return + } + + // Delete characters in text proxy. + for _ in 0 ..< currentPrefix.count { + proxy.deleteBackward() } } @@ -842,14 +902,11 @@ class KeyboardViewController: UIInputViewController { func handleDeleteButtonPressed() { if [.idle, .selectCommand, .alreadyPlural, .invalid].contains(commandState) { proxy.deleteBackward() - } else if [.translate, .conjugate, .plural].contains(commandState) && !(allPrompts.contains(commandBar.text!) || allColoredPrompts.contains(commandBar.attributedText!)) { - guard - let inputText = commandBar.text, - !inputText.isEmpty - else { + } else if [.translate, .conjugate, .plural].contains(commandState) && !(allPrompts.contains(commandBar.text ?? "") || allColoredPrompts.contains(commandBar.attributedText ?? NSAttributedString())) { + guard let inputText = commandBar.text, !inputText.isEmpty else { return } - commandBar.text = commandBar.text!.deletePriorToCursor() + commandBar.text = inputText.deletePriorToCursor() } else { backspaceTimer?.invalidate() backspaceTimer = nil @@ -1355,8 +1412,8 @@ class KeyboardViewController: UIInputViewController { /// Assign the verb conjugations that will be selectable in the conjugation display. func assignVerbConjStates() { var conjugationStateFxn: () -> String = deGetConjugationState - if controllerLanguage != "Swedish" { - conjugationStateFxn = keyboardConjStateDict[controllerLanguage] as! () -> String + if let conjugationFxn = keyboardConjStateDict[controllerLanguage] as? () -> String { + conjugationStateFxn = conjugationFxn } if !["Russian", "Swedish"].contains(controllerLanguage) { @@ -1366,7 +1423,6 @@ class KeyboardViewController: UIInputViewController { formFPP = conjugationStateFxn() + "FPP" formSPP = conjugationStateFxn() + "SPP" formTPP = conjugationStateFxn() + "TPP" - } else if controllerLanguage == "Russian" { if formsDisplayDimensions == .view3x2 { formFPS = ruGetConjugationState() + "FPS" @@ -1381,7 +1437,6 @@ class KeyboardViewController: UIInputViewController { formBottomLeft = "pastNeutral" formBottomRight = "pastPlural" } - } else if controllerLanguage == "Swedish" { let swedishTenses = svGetConjugationState() @@ -1400,9 +1455,11 @@ class KeyboardViewController: UIInputViewController { // Set the view title and its labels. var conjugationTitleFxn: () -> String = deGetConjugationTitle var conjugationLabelsFxn: () -> Void = deSetConjugationLabels - if controllerLanguage != "Swedish" { - conjugationTitleFxn = keyboardConjTitleDict[controllerLanguage] as! () -> String - conjugationLabelsFxn = keyboardConjLabelDict[controllerLanguage] as! () -> Void + if let titleFxn = keyboardConjTitleDict[controllerLanguage] as? () -> String { + conjugationTitleFxn = titleFxn + } + if let labelsFxn = keyboardConjLabelDict[controllerLanguage] as? () -> Void { + conjugationLabelsFxn = labelsFxn } if !["Russian", "Swedish"].contains(controllerLanguage) { @@ -1439,8 +1496,10 @@ class KeyboardViewController: UIInputViewController { } // Populate conjugation view buttons. + let query = "SELECT * FROM verbs WHERE verb = ?" + let args = [verbToConjugate] let outputCols = allConjugations - let conjugationsToDisplay = LanguageDBManager.shared.queryVerb(of: verbConjugated, with: outputCols) + let conjugationsToDisplay = queryDBRow(query: query, outputCols: outputCols, args: args) for index in 0 ..< allConjugations.count { if conjugationsToDisplay[index] == "" { // Assign the invalid message if the conjugation isn't present in the directory. @@ -1525,12 +1584,276 @@ class KeyboardViewController: UIInputViewController { } } + func setKeywidth() { + // keyWidth determined per keyboard by the top row. + if isLandscapeView { + if DeviceType.isPhone { + letterKeyWidth = (UIScreen.main.bounds.height - 5) / CGFloat(letterKeys[0].count) * scalarLetterNumSymKeyWidthLandscapeViewPhone + numSymKeyWidth = (UIScreen.main.bounds.height - 5) / CGFloat(numberKeys[0].count) * scalarLetterNumSymKeyWidthLandscapeViewPhone + } else if DeviceType.isPad { + letterKeyWidth = (UIScreen.main.bounds.height - 5) / CGFloat(letterKeys[0].count) * scalarLetterNumSymKeyWidthLandscapeViewPad + if !usingExpandedKeyboard { + numSymKeyWidth = (UIScreen.main.bounds.height - 5) / CGFloat(numberKeys[0].count) * scalarLetterNumSymKeyWidthLandscapeViewPad + } + } + } else { + letterKeyWidth = (UIScreen.main.bounds.width - 6) / CGFloat(letterKeys[0].count) * scalarLetterNumSymKeyWidth + numSymKeyWidth = (UIScreen.main.bounds.width - 6) / CGFloat(symbolKeys[0].count) * scalarLetterNumSymKeyWidth + } + } + + func setKeyPadding() { + let numRows = keyboard.count + for row in 0 ..< numRows { + for idx in 0 ..< keyboard[row].count { + // Set up button as a key with its values and properties. + let btn = KeyboardKey(type: .custom) + btn.row = row + btn.idx = idx + btn.style() + btn.setChar() + btn.setCharSize() + + let key: String = btn.key + + // Pad before key is added. + var leftPadding = CGFloat(0) + if DeviceType.isPhone + && key == "y" + && ["German", "Swedish"].contains(controllerLanguage) + && commandState != .translate + { + leftPadding = keyWidth / 3 + addPadding(to: stackView2, width: leftPadding, key: "y") + } + if DeviceType.isPhone + && key == "a" + && (controllerLanguage == "Portuguese" + || controllerLanguage == "Italian" + || commandState == .translate) + { + leftPadding = keyWidth / 4 + addPadding(to: stackView1, width: leftPadding, key: "a") + } + if DeviceType.isPad + && key == "a" + && !usingExpandedKeyboard + && (controllerLanguage == "Portuguese" + || controllerLanguage == "Italian" + || commandState == .translate) + { + leftPadding = keyWidth / 3 + addPadding(to: stackView1, width: leftPadding, key: "a") + } + if DeviceType.isPad + && key == "@" + && !usingExpandedKeyboard + && (controllerLanguage == "Portuguese" + || controllerLanguage == "Italian" + || commandState == .translate) + { + leftPadding = keyWidth / 3 + addPadding(to: stackView1, width: leftPadding, key: "@") + } + if DeviceType.isPad + && key == "€" + && !usingExpandedKeyboard + && (controllerLanguage == "Portuguese" + || commandState == .translate) + { + leftPadding = keyWidth / 3 + addPadding(to: stackView1, width: leftPadding, key: "€") + } + + keyboardKeys.append(btn) + if !usingExpandedKeyboard { + switch row { + case 0: stackView0.addArrangedSubview(btn) + case 1: stackView1.addArrangedSubview(btn) + case 2: stackView2.addArrangedSubview(btn) + case 3: stackView3.addArrangedSubview(btn) + default: + break + } + } else { + switch row { + case 0: stackViewNum.addArrangedSubview(btn) + case 1: stackView0.addArrangedSubview(btn) + case 2: stackView1.addArrangedSubview(btn) + case 3: stackView2.addArrangedSubview(btn) + case 4: stackView3.addArrangedSubview(btn) + default: + break + } + } + + // Special key styling. + if key == "delete" { + let deleteLongPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(deleteLongPressed(_:))) + btn.addGestureRecognizer(deleteLongPressRecognizer) + } + + if key == "selectKeyboard" { + selectKeyboardButton = btn + selectKeyboardButton.addTarget( + self, + action: #selector(handleInputModeList(from:with:)), + for: .allTouchEvents + ) + styleIconBtn(btn: btn, color: keyCharColor, iconName: "globe") + } + + if key == "hideKeyboard" { + styleIconBtn(btn: btn, color: keyCharColor, iconName: "keyboard.chevron.compact.down") + } + + if key == SpecialKeys.indent { + styleIconBtn(btn: btn, color: keyCharColor, iconName: "arrow.forward.to.line") + } + + if key == SpecialKeys.capsLock { + styleIconBtn(btn: btn, color: keyCharColor, iconName: "capslock") + } + + if key == "shift" { + styleIconBtn(btn: btn, color: keyCharColor, iconName: "shift") + } + + if key == "return" { + if [.translate, .conjugate, .plural].contains(commandState) { + styleIconBtn(btn: btn, color: keyCharColor, iconName: "arrowtriangle.right.fill") + } else { + styleIconBtn(btn: btn, color: keyCharColor, iconName: "arrow.turn.down.left") + } + } + + if key == "delete" { + styleIconBtn(btn: btn, color: keyCharColor, iconName: "delete.left") + } + + // Setting key pop functionality. + let keyHoldPop = UILongPressGestureRecognizer( + target: self, + action: #selector(genHoldPopUpView(sender:)) + ) + keyHoldPop.minimumPressDuration = 0.125 + + if allNonSpecialKeys.contains(key) { + btn.addTarget(self, action: #selector(genPopUpView), for: .touchDown) + btn.addGestureRecognizer(keyHoldPop) + } + + // Pad after key is added. + var rightPadding = CGFloat(0) + if DeviceType.isPhone + && key == "m" + && ["German", "Swedish"].contains(controllerLanguage) + && commandState != .translate + { + rightPadding = keyWidth / 3 + addPadding(to: stackView2, width: rightPadding, key: "m") + } + if DeviceType.isPhone + && key == "l" + && (controllerLanguage == "Portuguese" + || controllerLanguage == "Italian" + || commandState == .translate) + { + rightPadding = keyWidth / 4 + addPadding(to: stackView1, width: rightPadding, key: "l") + } + + // Set the width of the key given device and the given key. + btn.adjustKeyWidth() + + // Update the button style. + btn.adjustButtonStyle() + + if key == "return" && proxy.keyboardType == .webSearch && ![.translate, .conjugate, .plural].contains(commandState) { + // Override background color from adjustKeyWidth for "search" blue for web searches. + styleIconBtn(btn: btn, color: .white.withAlphaComponent(0.9), iconName: "arrow.turn.down.left") + btn.backgroundColor = UIColor(red: 0.0 / 255.0, green: 121.0 / 255.0, blue: 251.0 / 255.0, alpha: 1.0) + } + + // Extend button touch areas. + var widthOfSpacing = CGFloat(0) + if keyboardState == .letters { + widthOfSpacing = ( + (UIScreen.main.bounds.width - 6.0) + - (CGFloat(letterKeys[0].count) * keyWidth) + ) / (CGFloat(letterKeys[0].count) + - 1.0 + ) + } else { + widthOfSpacing = ( + (UIScreen.main.bounds.width - 6.0) + - (CGFloat(usingExpandedKeyboard == true ? symbolKeys[0].count : numberKeys[0].count) * numSymKeyWidth) + ) / (CGFloat(letterKeys[0].count) + - 1.0 + ) + } + + switch row { + case 0: + btn.topShift = -5 + btn.bottomShift = -6 + case 1: + btn.topShift = -6 + btn.bottomShift = -6 + case 2: + btn.topShift = -6 + btn.bottomShift = -6 + case 3: + btn.topShift = -6 + btn.bottomShift = -5 + default: + break + } + + // Pad left and right based on if the button has been shifted. + if leftPadding == CGFloat(0) { + btn.leftShift = -(widthOfSpacing / 2) + } else { + btn.leftShift = -leftPadding + } + if rightPadding == CGFloat(0) { + btn.rightShift = -(widthOfSpacing / 2) + } else { + btn.rightShift = -rightPadding + } + + // Activate keyboard interface buttons. + activateBtn(btn: btn) + if key == "shift" || key == spaceBar || key == languageTextForSpaceBar { + btn.addTarget(self, action: #selector(keyMultiPress(_:event:)), for: .touchDownRepeat) + } + } + } + + // End padding. + switch keyboardState { + case .letters: + break + case .numbers: + break + case .symbols: + break + } + } + // MARK: Load Keys /// Loads the keys given the current constraints. func loadKeys() { // The name of the language keyboard that's referencing KeyboardViewController. controllerLanguage = classForCoder.description().components(separatedBy: ".KeyboardViewController")[0] + if let userDefaults = UserDefaults(suiteName: "group.scribe.userDefaultsContainer") { + if userDefaults.bool(forKey: "svAccentCharacters") { + disableAccentCharacters = true + } else { + disableAccentCharacters = false + } + } // Actions to be done only on initial loads. if isFirstKeyboardLoad { @@ -1557,19 +1880,45 @@ class KeyboardViewController: UIInputViewController { showKeyboardLanguage = true // Initialize the language database and create the autosuggestions lexicon. - //languageDB = openDBQueue() + languageDB = openDBQueue() // Add UILexicon words including unpaired first and last names from Contacts to autocompletions. - requestSupplementaryLexicon { (userLexicon: UILexicon!) in - for item in userLexicon.entries { - if item.documentText.count > 1 { - LanguageDBManager.shared.insertAutocompleteLexion(of: item.documentText) + let addToAutocompleteLexiconQuery = "INSERT OR IGNORE INTO autocomplete_lexicon (word) VALUES (?)" + requestSupplementaryLexicon { (userLexicon: UILexicon?) in + if let lexicon = userLexicon { + for item in lexicon.entries { + if item.documentText.count > 1 { + writeDBRow(query: addToAutocompleteLexiconQuery, args: [item.documentText]) + } } } } // Drop non-unique values in case the lexicon has added words that were already present. - LanguageDBManager.shared.deleteNonUniqueAutosuggestions() + let dropNonUniqueAutosuggestionsQuery = """ + DELETE FROM autocomplete_lexicon + WHERE rowid NOT IN ( + SELECT + MIN(rowid) + + FROM + autocomplete_lexicon + + GROUP BY + word + ) + """ + do { + try languageDB.write { db in + try db.execute(sql: dropNonUniqueAutosuggestionsQuery) + } + } catch let error as DatabaseError { + let errorMessage = error.message + let errorSQL = error.sql + print( + "An error '\(String(describing: errorMessage))' occurred in the query: \(String(describing: errorSQL))" + ) + } catch {} } setKeyboard() @@ -1590,32 +1939,23 @@ class KeyboardViewController: UIInputViewController { keyboardKeys.forEach { $0.removeFromSuperview() } paddingViews.forEach { $0.removeFromSuperview() } - // keyWidth determined per keyboard by the top row. - if isLandscapeView { - if DeviceType.isPhone { - letterKeyWidth = (UIScreen.main.bounds.height - 5) / CGFloat(letterKeys[0].count) * 1.5 - numSymKeyWidth = (UIScreen.main.bounds.height - 5) / CGFloat(numberKeys[0].count) * 1.5 - } else if DeviceType.isPad { - letterKeyWidth = (UIScreen.main.bounds.height - 5) / CGFloat(letterKeys[0].count) * 1.2 - numSymKeyWidth = (UIScreen.main.bounds.height - 5) / CGFloat(numberKeys[0].count) * 1.2 - } - } else { - letterKeyWidth = (UIScreen.main.bounds.width - 6) / CGFloat(letterKeys[0].count) * 0.9 - numSymKeyWidth = (UIScreen.main.bounds.width - 6) / CGFloat(symbolKeys[0].count) * 0.9 - } + setKeywidth() // Derive keyboard given current states and set widths. switch keyboardState { case .letters: keyboard = letterKeys keyWidth = letterKeyWidth + // Auto-capitalization if the cursor is at the start of the proxy. - if proxy.documentContextBeforeInput?.count == 0 { + if let documentContext = proxy.documentContextBeforeInput, documentContext.isEmpty { shiftButtonState = .shift } + case .numbers: keyboard = numberKeys keyWidth = numSymKeyWidth + case .symbols: keyboard = symbolKeys keyWidth = numSymKeyWidth @@ -1624,19 +1964,19 @@ class KeyboardViewController: UIInputViewController { // Derive corner radii. if DeviceType.isPhone { if isLandscapeView { - keyCornerRadius = keyWidth / 9 - commandKeyCornerRadius = keyWidth / 5 + keyCornerRadius = keyWidth / scalarKeyCornerRadiusLandscapeViewPhone + commandKeyCornerRadius = keyWidth / scalarCommandKeyCornerRadiusLandscapeViewPhone } else { - keyCornerRadius = keyWidth / 6 - commandKeyCornerRadius = keyWidth / 3 + keyCornerRadius = keyWidth / scalarKeyCornerRadiusPhone + commandKeyCornerRadius = keyWidth / scalarCommandKeyCornerRadiusPhone } } else if DeviceType.isPad { if isLandscapeView { - keyCornerRadius = keyWidth / 12 - commandKeyCornerRadius = keyWidth / 7.5 + keyCornerRadius = keyWidth / scalarKeyCornerRadiusLandscapeViewPad + commandKeyCornerRadius = keyWidth / scalarCommandKeyCornerRadiusLandscapeViewPad } else { - keyCornerRadius = keyWidth / 9 - commandKeyCornerRadius = keyWidth / 5 + keyCornerRadius = keyWidth / scalarKeyCornerRadiusPad + commandKeyCornerRadius = keyWidth / scalarCommandKeyCornerRadiusPad } } @@ -1648,16 +1988,30 @@ class KeyboardViewController: UIInputViewController { view?.isLayoutMarginsRelativeArrangement = true // Set edge insets for stack views to provide vertical key spacing. - if view == stackViewNum { - view?.layoutMargins = UIEdgeInsets(top: 3, left: 0, bottom: 4, right: 0) - } else if view == stackView0 { - view?.layoutMargins = UIEdgeInsets(top: 3, left: 0, bottom: 8, right: 0) - } else if view == stackView1 { - view?.layoutMargins = UIEdgeInsets(top: 5, left: 0, bottom: 6, right: 0) - } else if view == stackView2 { - view?.layoutMargins = UIEdgeInsets(top: 5, left: 0, bottom: 6, right: 0) - } else if view == stackView3 { - view?.layoutMargins = UIEdgeInsets(top: 6, left: 0, bottom: 5, right: 0) + if DeviceType.isPad { + if view == stackViewNum { + view?.layoutMargins = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0) + } else if view == stackView0 { + view?.layoutMargins = UIEdgeInsets(top: 2, left: 0, bottom: 6, right: 0) + } else if view == stackView1 { + view?.layoutMargins = UIEdgeInsets(top: 3, left: 0, bottom: 4, right: 0) + } else if view == stackView2 { + view?.layoutMargins = UIEdgeInsets(top: 3, left: 0, bottom: 4, right: 0) + } else if view == stackView3 { + view?.layoutMargins = UIEdgeInsets(top: 4, left: 0, bottom: 3, right: 0) + } + } else { + if view == stackViewNum { + view?.layoutMargins = UIEdgeInsets(top: 3, left: 0, bottom: 4, right: 0) + } else if view == stackView0 { + view?.layoutMargins = UIEdgeInsets(top: 3, left: 0, bottom: 8, right: 0) + } else if view == stackView1 { + view?.layoutMargins = UIEdgeInsets(top: 5, left: 0, bottom: 6, right: 0) + } else if view == stackView2 { + view?.layoutMargins = UIEdgeInsets(top: 5, left: 0, bottom: 6, right: 0) + } else if view == stackView3 { + view?.layoutMargins = UIEdgeInsets(top: 6, left: 0, bottom: 5, right: 0) + } } } @@ -1671,13 +2025,13 @@ class KeyboardViewController: UIInputViewController { deactivateConjugationDisplay() if DeviceType.isPhone { - translateKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.435) - conjugateKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.435) - pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.435) + translateKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarCommandKeyHeightPhone) + conjugateKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarCommandKeyHeightPhone) + pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarCommandKeyHeightPhone) } else if DeviceType.isPad { - translateKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) - conjugateKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) - pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * 0.475) + translateKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarCommandKeyHeightPad) + conjugateKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarCommandKeyHeightPad) + pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarCommandKeyHeightPad) } if commandState == .selectCommand { @@ -1731,246 +2085,7 @@ class KeyboardViewController: UIInputViewController { } } - let numRows = keyboard.count - for row in 0 ..< numRows { - for idx in 0 ..< keyboard[row].count { - // Set up button as a key with its values and properties. - let btn = KeyboardKey(type: .custom) - btn.row = row - btn.idx = idx - btn.style() - btn.setChar() - btn.setCharSize() - - let key: String = btn.key - - // Pad before key is added. - var leftPadding = CGFloat(0) - if DeviceType.isPhone - && key == "y" - && ["German", "Swedish"].contains(controllerLanguage) - && commandState != .translate - { - leftPadding = keyWidth / 3 - addPadding(to: stackView2, width: leftPadding, key: "y") - } - if DeviceType.isPhone - && key == "a" - && (controllerLanguage == "Portuguese" - || controllerLanguage == "Italian" - || commandState == .translate) - { - leftPadding = keyWidth / 4 - addPadding(to: stackView1, width: leftPadding, key: "a") - } - if DeviceType.isPad - && key == "a" - && (controllerLanguage == "Portuguese" - || controllerLanguage == "Italian" - || commandState == .translate) - { - leftPadding = keyWidth / 3 - addPadding(to: stackView1, width: leftPadding, key: "a") - } - if DeviceType.isPad - && key == "@" - && (controllerLanguage == "Portuguese" - || controllerLanguage == "Italian" - || commandState == .translate) - { - leftPadding = keyWidth / 3 - addPadding(to: stackView1, width: leftPadding, key: "@") - } - if DeviceType.isPad - && key == "$" - && controllerLanguage == "Italian" - { - leftPadding = keyWidth / 3 - addPadding(to: stackView1, width: leftPadding, key: "$") - } - if DeviceType.isPad - && key == "€" - && (controllerLanguage == "Portuguese" - || commandState == .translate) - { - leftPadding = keyWidth / 3 - addPadding(to: stackView1, width: leftPadding, key: "€") - } - - keyboardKeys.append(btn) - if !usingExpandedKeyboard { - switch row { - case 0: stackView0.addArrangedSubview(btn) - case 1: stackView1.addArrangedSubview(btn) - case 2: stackView2.addArrangedSubview(btn) - case 3: stackView3.addArrangedSubview(btn) - default: - break - } - } else { - switch row { - case 0: stackViewNum.addArrangedSubview(btn) - case 1: stackView0.addArrangedSubview(btn) - case 2: stackView1.addArrangedSubview(btn) - case 3: stackView2.addArrangedSubview(btn) - case 4: stackView3.addArrangedSubview(btn) - default: - break - } - } - - // Special key styling. - if key == "delete" { - let deleteLongPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(deleteLongPressed(_:))) - btn.addGestureRecognizer(deleteLongPressRecognizer) - } - - if key == "selectKeyboard" { - selectKeyboardButton = btn - selectKeyboardButton.addTarget( - self, - action: #selector(handleInputModeList(from:with:)), - for: .allTouchEvents - ) - styleIconBtn(btn: btn, color: keyCharColor, iconName: "globe") - } - - if key == "hideKeyboard" { - styleIconBtn(btn: btn, color: keyCharColor, iconName: "keyboard.chevron.compact.down") - } - - if key == SpecialKeys.indent { - styleIconBtn(btn: btn, color: keyCharColor, iconName: "arrow.forward.to.line") - } - - if key == SpecialKeys.capsLock { - styleIconBtn(btn: btn, color: keyCharColor, iconName: "capslock") - } - - if key == "shift" { - styleIconBtn(btn: btn, color: keyCharColor, iconName: "shift") - } - - if key == "return" { - if [.translate, .conjugate, .plural].contains(commandState) { - styleIconBtn(btn: btn, color: keyCharColor, iconName: "arrowtriangle.right.fill") - } else { - styleIconBtn(btn: btn, color: keyCharColor, iconName: "arrow.turn.down.left") - } - } - - if key == "delete" { - styleIconBtn(btn: btn, color: keyCharColor, iconName: "delete.left") - } - - // Setting key pop functionality. - let keyHoldPop = UILongPressGestureRecognizer( - target: self, - action: #selector(genHoldPopUpView(sender:)) - ) - keyHoldPop.minimumPressDuration = 0.125 - - if allNonSpecialKeys.contains(key) { - btn.addTarget(self, action: #selector(genPopUpView), for: .touchDown) - btn.addGestureRecognizer(keyHoldPop) - } - - // Pad after key is added. - var rightPadding = CGFloat(0) - if DeviceType.isPhone - && key == "m" - && ["German", "Swedish"].contains(controllerLanguage) - && commandState != .translate - { - rightPadding = keyWidth / 3 - addPadding(to: stackView2, width: rightPadding, key: "m") - } - if DeviceType.isPhone - && key == "l" - && (controllerLanguage == "Portuguese" - || controllerLanguage == "Italian" - || commandState == .translate) - { - rightPadding = keyWidth / 4 - addPadding(to: stackView1, width: rightPadding, key: "l") - } - - // Set the width of the key given device and the given key. - btn.adjustKeyWidth() - - // Update the button style. - btn.adjustButtonStyle() - - if key == "return" && proxy.keyboardType == .webSearch && ![.translate, .conjugate, .plural].contains(commandState) { - // Override background color from adjustKeyWidth for "search" blue for web searches. - styleIconBtn(btn: btn, color: .white.withAlphaComponent(0.9), iconName: "arrow.turn.down.left") - btn.backgroundColor = UIColor(red: 0.0 / 255.0, green: 121.0 / 255.0, blue: 251.0 / 255.0, alpha: 1.0) - } - - // Extend button touch areas. - var widthOfSpacing = CGFloat(0) - if keyboardState == .letters { - widthOfSpacing = ( - (UIScreen.main.bounds.width - 6.0) - - (CGFloat(letterKeys[0].count) * keyWidth) - ) / (CGFloat(letterKeys[0].count) - - 1.0 - ) - } else { - widthOfSpacing = ( - (UIScreen.main.bounds.width - 6.0) - - (CGFloat(usingExpandedKeyboard == true ? symbolKeys[0].count : numberKeys[0].count) * numSymKeyWidth) - ) / (CGFloat(letterKeys[0].count) - - 1.0 - ) - } - - switch row { - case 0: - btn.topShift = -5 - btn.bottomShift = -6 - case 1: - btn.topShift = -6 - btn.bottomShift = -6 - case 2: - btn.topShift = -6 - btn.bottomShift = -6 - case 3: - btn.topShift = -6 - btn.bottomShift = -5 - default: - break - } - - // Pad left and right based on if the button has been shifted. - if leftPadding == CGFloat(0) { - btn.leftShift = -(widthOfSpacing / 2) - } else { - btn.leftShift = -leftPadding - } - if rightPadding == CGFloat(0) { - btn.rightShift = -(widthOfSpacing / 2) - } else { - btn.rightShift = -rightPadding - } - - // Activate keyboard interface buttons. - activateBtn(btn: btn) - if key == "shift" || key == spaceBar || key == languageTextForSpaceBar { - btn.addTarget(self, action: #selector(keyMultiPress(_:event:)), for: .touchDownRepeat) - } - } - } - - // End padding. - switch keyboardState { - case .letters: - break - case .numbers: - break - case .symbols: - break - } + setKeyPadding() } else { // Load conjugation view. @@ -2021,23 +2136,27 @@ class KeyboardViewController: UIInputViewController { func setCommaAndPeriodKeysConditionally() { let langCode = languagesAbbrDict[controllerLanguage] ?? "unknown" - let userDefaults = UserDefaults(suiteName: "group.scribe.userDefaultsContainer")! - let dictionaryKey = langCode + "CommaAndPeriod" - let letterKeysHaveCommaPeriod = userDefaults.bool(forKey: dictionaryKey) + if let userDefaults = UserDefaults(suiteName: "group.scribe.userDefaultsContainer") { + let dictionaryKey = langCode + "CommaAndPeriod" + let letterKeysHaveCommaPeriod = userDefaults.bool(forKey: dictionaryKey) - if letterKeysHaveCommaPeriod { - let spaceIndex = letterKeys[3].firstIndex(where: { $0 == "space" }) - letterKeys[3].insert(",", at: spaceIndex!) - letterKeys[3].insert(".", at: spaceIndex! + 2) + if letterKeysHaveCommaPeriod { + let spaceIndex = letterKeys[3].firstIndex(where: { $0 == "space" }) + letterKeys[3].insert(",", at: spaceIndex!) + letterKeys[3].insert(".", at: spaceIndex! + 2) + } } } func emojiAutosuggestIsEnabled() -> Bool { let langCode = languagesAbbrDict[controllerLanguage] ?? "unknown" - let userDefaults = UserDefaults(suiteName: "group.scribe.userDefaultsContainer")! - let dictionaryKey = langCode + "EmojiAutosuggest" + if let userDefaults = UserDefaults(suiteName: "group.scribe.userDefaultsContainer") { + let dictionaryKey = langCode + "EmojiAutosuggest" - return userDefaults.bool(forKey: dictionaryKey) + return userDefaults.bool(forKey: dictionaryKey) + } else { + return true // return the default value + } } // MARK: Button Actions @@ -2308,16 +2427,22 @@ class KeyboardViewController: UIInputViewController { if let wordSelected = proxy.selectedText { wordToCheck = wordSelected } else { - wordsTyped = proxy.documentContextBeforeInput!.components(separatedBy: " ") - let lastWordTyped = wordsTyped.secondToLast() - if !languagesWithCapitalizedNouns.contains(controllerLanguage) { - wordToCheck = lastWordTyped!.lowercased() - } else { - wordToCheck = lastWordTyped! + if let contextBeforeInput = proxy.documentContextBeforeInput { + wordsTyped = contextBeforeInput.components(separatedBy: " ") + let lastWordTyped = wordsTyped.secondToLast() + if !languagesWithCapitalizedNouns.contains(controllerLanguage) { + wordToCheck = lastWordTyped!.lowercased() + } else { + wordToCheck = lastWordTyped! + } } } - let prepForm = LanguageDBManager.shared.queryPrepForm(of: wordToCheck)[0] - hasPrepForm = prepForm != "" + let prepCaseQuery = "SELECT * FROM prepositions WHERE preposition = ?" + let prepCaseArgs = [wordToCheck.lowercased()] + let outputCols = ["form"] + + let prepForm = queryDBRow(query: prepCaseQuery, outputCols: outputCols, args: prepCaseArgs)[0] + hasPrepForm = !prepForm.isEmpty if hasPrepForm { resetCaseDeclensionState() commandState = .selectCaseDeclension @@ -2332,7 +2457,7 @@ class KeyboardViewController: UIInputViewController { annotationBtns[i].backgroundColor = annotationColors[i] } let emojisToSelectFrom = "🥳🎉" - let emojis = String((0 ..< 3).map { _ in emojisToSelectFrom.randomElement()! }) + let emojis = String((0 ..< 3).compactMap { _ in emojisToSelectFrom.randomElement() }) sender.setTitle(emojis, for: .normal) return @@ -2360,13 +2485,15 @@ class KeyboardViewController: UIInputViewController { conditionallySetAutoActionBtns() } else { // Shift state if the user presses delete when the prompt is present. - if allPrompts.contains((commandBar?.text!)!) || allColoredPrompts.contains(commandBar.attributedText!) { - shiftButtonState = .shift // Auto-capitalization - loadKeys() - // Function call required due to return. - // Not including means placeholder is never added on last delete action. - commandBar.conditionallyAddPlaceholder() - return + if let commandBarText = commandBar?.text, let commandBarAttributedText = commandBar?.attributedText { + if allPrompts.contains(commandBarText) || allColoredPrompts.contains(commandBarAttributedText) { + shiftButtonState = .shift // Auto-capitalization + loadKeys() + // Function call required due to return. + // Not including means placeholder is never added on last delete action. + commandBar.conditionallyAddPlaceholder() + return + } } handleDeleteButtonPressed() @@ -2397,16 +2524,14 @@ class KeyboardViewController: UIInputViewController { changeKeyboardToLetterKeys() } } else { - commandBar.text! = (commandBar?.text!.insertPriorToCursor(char: " "))! - if [ - ". " + commandCursor, - "? " + commandCursor, - "! " + commandCursor, - ].contains(String(commandBar.text!.suffix(3))) { - shiftButtonState = .shift - } - if keyboardState != .letters { - changeKeyboardToLetterKeys() + if let commandBarText = commandBar?.text { + commandBar?.text = commandBarText.insertPriorToCursor(char: " ") + if [". " + commandCursor, "? " + commandCursor, "! " + commandCursor].contains(String(commandBarText.suffix(3))) { + shiftButtonState = .shift + } + if keyboardState != .letters { + changeKeyboardToLetterKeys() + } } } @@ -2427,7 +2552,9 @@ class KeyboardViewController: UIInputViewController { if ![.translate, .conjugate, .plural].contains(commandState) { proxy.insertText("'") } else { - commandBar.text! = (commandBar.text!.insertPriorToCursor(char: "'")) + if let commandBarText = commandBar.text { + commandBar.text = commandBarText.insertPriorToCursor(char: "'") + } } changeKeyboardToLetterKeys() @@ -2440,7 +2567,9 @@ class KeyboardViewController: UIInputViewController { proxy.insertText(keyToDisplay) } } else { - commandBar.text = commandBar.text!.insertPriorToCursor(char: keyToDisplay) + if let commandBarText = commandBar.text { + commandBar.text = commandBarText.insertPriorToCursor(char: keyToDisplay) + } } case ",", ".", "!", "?": @@ -2450,7 +2579,9 @@ class KeyboardViewController: UIInputViewController { } proxy.insertText(keyToDisplay) } else { - commandBar.text = commandBar.text!.insertPriorToCursor(char: keyToDisplay) + if let commandBarText = commandBar.text { + commandBar.text = commandBarText.insertPriorToCursor(char: keyToDisplay) + } } case "shift": @@ -2497,7 +2628,9 @@ class KeyboardViewController: UIInputViewController { if [.idle, .selectCommand, .alreadyPlural, .invalid].contains(commandState) { proxy.insertText(keyToDisplay) } else { - commandBar.text = commandBar.text!.insertPriorToCursor(char: keyToDisplay) + if let currentText = commandBar.text { + commandBar.text = currentText.insertPriorToCursor(char: keyToDisplay) + } } } @@ -2593,50 +2726,50 @@ class KeyboardViewController: UIInputViewController { @objc func keyMultiPress(_ sender: UIButton, event: UIEvent) { guard var originalKey = sender.layer.value(forKey: "original") as? String else { return } - let touch: UITouch = event.allTouches!.first! - - // Caps lock given two taps of shift. - if touch.tapCount == 2 && originalKey == "shift" && capsLockPossible { - switchToFullCaps() - } - - // To make sure that the user can still use the double space period shortcut after numbers and symbols. - let punctuationThatCancelsShortcut = ["?", "!", ",", ".", ":", ";", "-"] - if originalKey != "shift" && proxy.documentContextBeforeInput?.count != 1 && ![.translate, .conjugate, .plural].contains(commandState) { - let charBeforeSpace = String(Array(proxy.documentContextBeforeInput!).secondToLast()!) - if punctuationThatCancelsShortcut.contains(charBeforeSpace) { - originalKey = "Cancel shortcut" + if let touches = event.allTouches, let touch = touches.first { + // Caps lock given two taps of shift. + if touch.tapCount == 2 && originalKey == "shift" && capsLockPossible { + switchToFullCaps() } - } else if [.translate, .conjugate, .plural].contains(commandState) { - let charBeforeSpace = String(Array((commandBar?.text!)!).secondToLast()!) - if punctuationThatCancelsShortcut.contains(charBeforeSpace) { - originalKey = "Cancel shortcut" + + // To make sure that the user can still use the double space period shortcut after numbers and symbols. + let punctuationThatCancelsShortcut = ["?", "!", ",", ".", ":", ";", "-"] + if originalKey != "shift" && proxy.documentContextBeforeInput?.count != 1 && ![.translate, .conjugate, .plural].contains(commandState) { + let charBeforeSpace = String(Array(proxy.documentContextBeforeInput!).secondToLast()!) + if punctuationThatCancelsShortcut.contains(charBeforeSpace) { + originalKey = "Cancel shortcut" + } + } else if [.translate, .conjugate, .plural].contains(commandState) { + let charBeforeSpace = String(Array((commandBar?.text!)!).secondToLast()!) + if punctuationThatCancelsShortcut.contains(charBeforeSpace) { + originalKey = "Cancel shortcut" + } } - } - // Double space period shortcut. - if touch.tapCount == 2 - && (originalKey == spaceBar || originalKey == languageTextForSpaceBar) - && proxy.documentContextBeforeInput?.count != 1 - && doubleSpacePeriodPossible - { - // The fist condition prevents a period if the prior characters are spaces as the user wants a series of spaces. - if proxy.documentContextBeforeInput?.suffix(2) != " " && ![.translate, .conjugate, .plural].contains(commandState) { - proxy.deleteBackward() - proxy.insertText(". ") - emojisToShow = .zero // was showing empty emoji spots - keyboardState = .letters - shiftButtonState = .shift - loadKeys() + // Double space period shortcut. + if touch.tapCount == 2 + && (originalKey == spaceBar || originalKey == languageTextForSpaceBar) + && proxy.documentContextBeforeInput?.count != 1 + && doubleSpacePeriodPossible + { // The fist condition prevents a period if the prior characters are spaces as the user wants a series of spaces. - } else if commandBar.text!.suffix(2) != " " && [.translate, .conjugate, .plural].contains(commandState) { - commandBar.text! = (commandBar?.text!.deletePriorToCursor())! - commandBar.text! = (commandBar?.text!.insertPriorToCursor(char: ". "))! - keyboardState = .letters - shiftButtonState = .shift - loadKeys() + if proxy.documentContextBeforeInput?.suffix(2) != " " && ![.translate, .conjugate, .plural].contains(commandState) { + proxy.deleteBackward() + proxy.insertText(". ") + emojisToShow = .zero // was showing empty emoji spots + keyboardState = .letters + shiftButtonState = .shift + loadKeys() + // The fist condition prevents a period if the prior characters are spaces as the user wants a series of spaces. + } else if commandBar.text!.suffix(2) != " " && [.translate, .conjugate, .plural].contains(commandState) { + commandBar.text! = (commandBar?.text!.deletePriorToCursor())! + commandBar.text! = (commandBar?.text!.insertPriorToCursor(char: ". "))! + keyboardState = .letters + shiftButtonState = .shift + loadKeys() + } + // Show auto actions if the keyboard states dictate. + conditionallySetAutoActionBtns() } - // Show auto actions if the keyboard states dictate. - conditionallySetAutoActionBtns() } } @@ -2655,7 +2788,7 @@ class KeyboardViewController: UIInputViewController { /// - gesture: the gesture that was received. @objc func deleteLongPressed(_ gesture: UIGestureRecognizer) { // Prevent the command state prompt from being deleted. - if [.translate, .conjugate, .plural].contains(commandState) && allPrompts.contains((commandBar?.text!)!) { + if let commandBarText = commandBar?.text, [.translate, .conjugate, .plural].contains(commandState), allPrompts.contains(commandBarText) { gesture.state = .cancelled commandBar.conditionallyAddPlaceholder() } @@ -2774,9 +2907,9 @@ class KeyboardViewController: UIInputViewController { genAlternatesView(key: key) alternateBtnStartX = 5.0 - var alternatesBtnY = key.frame.height * 0.15 + var alternatesBtnY = key.frame.height * scalarAlternatesBtnYPhone if DeviceType.isPad { - alternatesBtnY = key.frame.height * 0.2 + alternatesBtnY = key.frame.height * scalarAlternatesBtnYPad } for char in alternateKeys { let alternateKey = KeyboardKey( diff --git a/Keyboards/KeyboardsBase/LoadData.swift b/Keyboards/KeyboardsBase/LoadData.swift new file mode 100644 index 00000000..539f8d4b --- /dev/null +++ b/Keyboards/KeyboardsBase/LoadData.swift @@ -0,0 +1,158 @@ +/** + * Function for loading in data to the keyboards. + * + * Copyright (C) 2023 Scribe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Foundation +import GRDB +import SwiftyJSON + +/// Loads a JSON file that contains grammatical information into a dictionary. +/// +/// - Parameters +/// - filename: the name of the JSON file to be loaded. +func loadJSON(filename fileName: String) -> JSON? { + guard let url = Bundle.main.url(forResource: fileName, withExtension: "json"), + let data = try? Data(contentsOf: url), + let jsonData = try? JSON(data: data) + else { + return nil + } + return jsonData +} + +/// Makes a connection to the language database given the value for controllerLanguage. +func openDBQueue() -> DatabaseQueue { + let dbName = "\(String(describing: get_iso_code(keyboardLanguage: controllerLanguage).uppercased()))LanguageData" + guard let dbPath = Bundle.main.path(forResource: dbName, ofType: "sqlite") else { + fatalError("Failed to locate database file.") + } + do { + let dbQueue = try DatabaseQueue(path: dbPath) + return dbQueue + } catch { + fatalError("Failed to initialize DatabaseQueue: \(error)") + } +} + +// Variable to be replaced with the result of openDBQueue. +var languageDB = try! DatabaseQueue() + +/// Returns a row from the language database given a query and arguments. +/// +/// - Parameters +/// - query: the query to run against the language database. +/// - outputCols: the columns from which the values should come. +/// - args: arguments to pass to `query`. +func queryDBRow(query: String, outputCols: [String], args: [String]) -> [String] { + var outputValues = [String]() + do { + try languageDB.read { db in + if let row = try Row.fetchOne(db, sql: query, arguments: StatementArguments(args)) { + for col in outputCols { + outputValues.append(row[col]) + } + } + } + } catch let error as DatabaseError { + let errorMessage = error.message + let errorSQL = error.sql + let errorArguments = error.arguments + print( + "An error '\(String(describing: errorMessage))' occurred in the query: \(String(describing: errorSQL)) (\(String(describing: errorArguments)))" + ) + } catch {} + + if outputValues == [String]() { + // Append an empty string so that we can check for it and trigger commandState = .invalid. + outputValues.append("") + } + + return outputValues +} + +/// Writes a row of a language database table given a query and arguments. +/// +/// - Parameters +/// - query: the query to run against the language database. +/// - args: arguments to pass to `query`. +func writeDBRow(query: String, args: StatementArguments) { + do { + try languageDB.write { db in + try db.execute( + sql: query, + arguments: args + ) + } + } catch let error as DatabaseError { + let errorMessage = error.message + let errorSQL = error.sql + let errorArguments = error.arguments + print( + "An error '\(String(describing: errorMessage))' occurred in the query: \(String(describing: errorSQL)) (\(String(describing: errorArguments)))" + ) + } catch {} +} + +/// Returns the next three words in the `autocomplete_lexicon` that follow a given word. +/// +/// - Parameters +/// - word: the word that autosuggestions should be returned for. +func queryAutocompletions(word: String) -> [String] { + var autocompletions = [String]() + + let autocompletionsQuery = """ + SELECT + word + + FROM + autocomplete_lexicon + + WHERE + LOWER(word) LIKE ? + + ORDER BY + word COLLATE NOCASE ASC + + LIMIT + 3 + """ + let patterns = ["\(word.lowercased())%"] + + do { + let rows = try languageDB.read { db in + try Row.fetchAll(db, sql: autocompletionsQuery, arguments: StatementArguments(patterns)) + } + for r in rows { + autocompletions.append(r["word"]) + } + } catch let error as DatabaseError { + let errorMessage = error.message + let errorSQL = error.sql + let errorArguments = error.arguments + print( + "An error '\(String(describing: errorMessage))' occurred in the query: \(String(describing: errorSQL)) (\(String(describing: errorArguments)))" + ) + } catch {} + + if autocompletions == [String]() { + // Append an empty string so that we can check for it and trigger nothing being shown. + autocompletions.append("") + } + + return autocompletions +} diff --git a/Keyboards/KeyboardsBase/ScribeFunctionality/Annotate.swift b/Keyboards/KeyboardsBase/ScribeFunctionality/Annotate.swift index c9bcf28d..3871dbc6 100644 --- a/Keyboards/KeyboardsBase/ScribeFunctionality/Annotate.swift +++ b/Keyboards/KeyboardsBase/ScribeFunctionality/Annotate.swift @@ -1,8 +1,21 @@ -// -// Annotate.swift -// -// Functions and elements that control word annotations. -// +/** + * Functions and elements that control word annotations. + * + * Copyright (C) 2023 Scribe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import UIKit @@ -38,11 +51,17 @@ let prepAnnotationConversionDict = [ /// - wordToAnnotate: the word that an annotation should be created for. /// - KVC: the keyboard view controller. func wordAnnotation(wordToAnnotate: String, KVC: KeyboardViewController) { - let nounForm = LanguageDBManager.shared.queryNounForm(of: wordToAnnotate)[0] - prepAnnotationForm = LanguageDBManager.shared.queryPrepForm(of: wordToAnnotate)[0] + let nounGenderQuery = "SELECT * FROM nouns WHERE noun = ?" + let prepCaseQuery = "SELECT * FROM prepositions WHERE preposition = ?" + let nounGenderArgs = [wordToAnnotate] + let prepCaseArgs = [wordToAnnotate.lowercased()] + let outputCols = ["form"] - hasNounForm = nounForm != "" - hasPrepForm = prepAnnotationForm != "" + let nounForm = queryDBRow(query: nounGenderQuery, outputCols: outputCols, args: nounGenderArgs)[0] + prepAnnotationForm = queryDBRow(query: prepCaseQuery, outputCols: outputCols, args: prepCaseArgs)[0] + + hasNounForm = !nounForm.isEmpty + hasPrepForm = !prepAnnotationForm.isEmpty annotationsToAssign = [String]() annotationBtns = [UIButton]() @@ -71,7 +90,7 @@ func wordAnnotation(wordToAnnotate: String, KVC: KeyboardViewController) { annotationBtn.styleSingleAnnotation(fullAnnotation: true) let emojisToSelectFrom = "🥳🎉" - let emojis = String((0 ..< 3).map { _ in emojisToSelectFrom.randomElement()! }) + let emojis = String((0 ..< 3).compactMap { _ in emojisToSelectFrom.randomElement() }) annotationBtn.setTitle(emojis, for: .normal) KVC.view.addSubview(annotationBtn) annotationBtns.append(annotationBtn) @@ -169,7 +188,9 @@ func wordAnnotation(wordToAnnotate: String, KVC: KeyboardViewController) { KVC.view.addSubview(annotationBtn) annotationBtns.append(annotationBtn) if nounFormToColorDict.keys.contains(annotationToDisplay) { - annotationColors.append(nounFormToColorDict[annotationsToAssign[i]]!) + if let annotationColor = nounFormToColorDict[annotationsToAssign[i]] { + annotationColors.append(annotationColor) + } } else { annotationColors.append(UITraitCollection.current.userInterfaceStyle == .light ? .black : .white) } @@ -208,7 +229,7 @@ func wordAnnotation(wordToAnnotate: String, KVC: KeyboardViewController) { /// - KVC: the keyboard view controller. func selectedWordAnnotation(KVC: KeyboardViewController) { wordToCheck = proxy.selectedText ?? "" - if wordToCheck.count > 0 { + if !wordToCheck.isEmpty { if !languagesWithCapitalizedNouns.contains(controllerLanguage) { wordToCheck = wordToCheck.lowercased() } @@ -249,9 +270,13 @@ func typedWordAnnotation(KVC: KeyboardViewController) { /// - index: the auto action key index that the annotation should be set for. /// - KVC: the keyboard view controller. func autoActionAnnotation(autoActionWord: String, index: Int, KVC: KeyboardViewController) { - let nounForm = LanguageDBManager.shared.queryNounForm(of: autoActionWord)[0] + let nounGenderQuery = "SELECT * FROM nouns WHERE noun = ?" + let nounGenderArgs = [autoActionWord] + let outputCols = ["form"] + + let nounForm = queryDBRow(query: nounGenderQuery, outputCols: outputCols, args: nounGenderArgs)[0] - hasNounForm = nounForm != "" + hasNounForm = !nounForm.isEmpty newAutoActionAnnotationsToAssign = [String]() newAutoActionAnnotationBtns = [UIButton]() @@ -331,9 +356,11 @@ func autoActionAnnotation(autoActionWord: String, index: Int, KVC: KeyboardViewC KVC.view.addSubview(annotationBtn) autoActionAnnotationBtns.append(annotationBtn) - newAutoActionAnnotationColors.append( - nounFormToColorDict[newAutoActionAnnotationsToAssign[i]]!.withAlphaComponent(0.75) - ) + if let annotationColor = nounFormToColorDict[newAutoActionAnnotationsToAssign[i]] { + let colorWithAlpha = annotationColor.withAlphaComponent(0.75) + newAutoActionAnnotationColors.append(colorWithAlpha) + } + setBtn( btn: annotationBtn, color: newAutoActionAnnotationColors[i], diff --git a/Keyboards/KeyboardsBase/ScribeFunctionality/Conjugate.swift b/Keyboards/KeyboardsBase/ScribeFunctionality/Conjugate.swift index b1ec572f..4c3badd2 100644 --- a/Keyboards/KeyboardsBase/ScribeFunctionality/Conjugate.swift +++ b/Keyboards/KeyboardsBase/ScribeFunctionality/Conjugate.swift @@ -1,8 +1,21 @@ -// -// Conjugation.swift -// -// Functions and elements that control the conjugation command. -// +/** + * Functions and elements that control the conjugation command. + * + * Copyright (C) 2023 Scribe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import UIKit @@ -47,9 +60,10 @@ let keyboardConjLabelDict: [String: Any] = [ func returnDeclension(keyPressed: UIButton) { let wordPressed = keyPressed.titleLabel?.text ?? "" - let keyName = keyPressed.layer.value( - forKey: "original" - ) as! String + var keyName = "" + if let originalKeyValue = keyPressed.layer.value(forKey: "original") as? String { + keyName = originalKeyValue + } if !(wordPressed.contains("/") || wordPressed.contains("∗")) { proxy.insertText(wordPressed + " ") @@ -180,17 +194,28 @@ func returnDeclension(keyPressed: UIButton) { /// - commandBar: the command bar into which an input was entered. func triggerVerbConjugation(commandBar: UILabel) -> Bool { // Cancel via a return press. - if commandBar.text! == conjugatePromptAndCursor || commandBar.text! == conjugatePromptAndPlaceholder { + if let commandBarText = commandBar.text, + commandBarText == conjugatePromptAndCursor || commandBarText == conjugatePromptAndCursor + { return false } - verbToConjugate = (commandBar.text!.substring(with: conjugatePrompt.count ..< (commandBar.text!.count) - 1)) + + if let commandBarText = commandBar.text { + let startIndex = commandBarText.index(commandBarText.startIndex, offsetBy: conjugatePrompt.count) + let endIndex = commandBarText.index(commandBarText.endIndex, offsetBy: -1) + verbToConjugate = String(commandBarText[startIndex ..< endIndex]) + } verbToConjugate = String(verbToConjugate.trailingSpacesTrimmed) // Check to see if the input was uppercase to return an uppercase conjugation. let firstLetter = verbToConjugate.substring(toIdx: 1) inputWordIsCapitalized = firstLetter.isUppercase + verbToConjugate = verbToConjugate.lowercased() - let verbInTable = LanguageDBManager.shared.queryVerb(of: verbConjugated)[0] + let query = "SELECT * FROM verbs WHERE verb = ?" + let args = [verbToConjugate] + let outputCols = ["verb"] + let verbInTable = queryDBRow(query: query, outputCols: outputCols, args: args)[0] return verbToConjugate == verbInTable } @@ -201,7 +226,6 @@ func triggerVerbConjugation(commandBar: UILabel) -> Bool { /// - keyPressed: the button pressed as sender. /// - requestedForm: the form that is triggered by the given key. func returnConjugation(keyPressed: UIButton, requestedForm: String) { - let outputCols = [requestedForm] if commandState == .selectCaseDeclension { returnDeclension(keyPressed: keyPressed) return @@ -212,11 +236,14 @@ func returnConjugation(keyPressed: UIButton, requestedForm: String) { if wordPressed == invalidCommandMsg { proxy.insertText("") } else if formsDisplayDimensions == .view3x2 { - wordToReturn = LanguageDBManager.shared.queryVerb(of: verbToConjugate, with: outputCols)[0] + let query = "SELECT * FROM verbs WHERE verb = ?" + let args = [verbToConjugate] + let outputCols = [requestedForm] + wordToReturn = queryDBRow(query: query, outputCols: outputCols, args: args)[0] potentialWordsToReturn = wordToReturn.components(separatedBy: " ") if inputWordIsCapitalized { - if controllerLanguage == "German" && potentialWordsToReturn.count == 2 { + if controllerLanguage == "German", potentialWordsToReturn.count == 2 { // Don't return a space as well as we have a perfect verb and the cursor will be between. proxy.insertText(wordToReturn.capitalize()) } else { @@ -226,7 +253,10 @@ func returnConjugation(keyPressed: UIButton, requestedForm: String) { proxy.insertText(wordToReturn + " ") } } else if formsDisplayDimensions == .view2x2 { - wordToReturn = LanguageDBManager.shared.queryVerb(of: verbToConjugate, with: outputCols)[0] + let query = "SELECT * FROM verbs WHERE verb = ?" + let args = [verbToConjugate] + let outputCols = [requestedForm] + wordToReturn = queryDBRow(query: query, outputCols: outputCols, args: args)[0] potentialWordsToReturn = wordToReturn.components(separatedBy: " ") if inputWordIsCapitalized { diff --git a/Keyboards/KeyboardsBase/ScribeFunctionality/Plural.swift b/Keyboards/KeyboardsBase/ScribeFunctionality/Plural.swift index e7bdc118..52b17cb6 100644 --- a/Keyboards/KeyboardsBase/ScribeFunctionality/Plural.swift +++ b/Keyboards/KeyboardsBase/ScribeFunctionality/Plural.swift @@ -1,8 +1,21 @@ -// -// Plural.swift -// -// Functions that control the plural command. -// +/** + * Functions that control the plural command. + * + * Copyright (C) 2023 Scribe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import UIKit @@ -12,34 +25,45 @@ import UIKit /// - commandBar: the command bar into which an input was entered. func queryPlural(commandBar: UILabel) { // Cancel via a return press. - if commandBar.text! == pluralPromptAndCursor || commandBar.text! == pluralPromptAndPlaceholder { + if let commandBarText = commandBar.text, + commandBarText == pluralPromptAndCursor || commandBarText == pluralPromptAndCursor + { return } - var noun: String = (commandBar.text!.substring( - with: pluralPrompt.count ..< ((commandBar.text!.count) - 1)) - ) + + var noun = "" + if let commandBarText = commandBar.text { + let startIndex = commandBarText.index(commandBarText.startIndex, offsetBy: pluralPrompt.count) + let endIndex = commandBarText.index(before: commandBarText.endIndex) + noun = String(commandBarText[startIndex ..< endIndex]) + } noun = String(noun.trailingSpacesTrimmed) // Check to see if the input was uppercase to return an uppercase plural. inputWordIsCapitalized = false if !languagesWithCapitalizedNouns.contains(controllerLanguage) { inputWordIsCapitalized = noun.substring(toIdx: 1).isUppercase + noun = noun.lowercased() } - wordToReturn = LanguageDBManager.shared.queryNounPlural(of: noun)[0] + let query = "SELECT * FROM nouns WHERE noun = ?" + let args = [noun] + let outputCols = ["plural"] + wordToReturn = queryDBRow(query: query, outputCols: outputCols, args: args)[0] - if wordToReturn != "" { - if wordToReturn != "isPlural" { - if inputWordIsCapitalized { - proxy.insertText(wordToReturn.capitalized + " ") - } else { - proxy.insertText(wordToReturn + " ") - } + guard !wordToReturn.isEmpty else { + commandState = .invalid + return + } + + if wordToReturn != "isPlural" { + if inputWordIsCapitalized { + proxy.insertText(wordToReturn.capitalized + " ") } else { - proxy.insertText(noun + " ") - commandState = .alreadyPlural + proxy.insertText(wordToReturn + " ") } } else { - commandState = .invalid + proxy.insertText(noun + " ") + commandState = .alreadyPlural } } diff --git a/Keyboards/KeyboardsBase/ScribeFunctionality/Translate.swift b/Keyboards/KeyboardsBase/ScribeFunctionality/Translate.swift index 2ed9c970..547f13b1 100644 --- a/Keyboards/KeyboardsBase/ScribeFunctionality/Translate.swift +++ b/Keyboards/KeyboardsBase/ScribeFunctionality/Translate.swift @@ -1,8 +1,21 @@ -// -// Translate.swift -// -// Functions that control the translate command. -// +/** + * Functions that control the translate command. + * + * Copyright (C) 2023 Scribe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import UIKit @@ -12,26 +25,36 @@ import UIKit /// - commandBar: the command bar into which an input was entered. func queryTranslation(commandBar: UILabel) { // Cancel via a return press. - if commandBar.text! == translatePromptAndCursor || commandBar.text! == translatePromptAndPlaceholder { + if let commandBarText = commandBar.text, + commandBarText == translatePromptAndCursor || commandBarText == translatePromptAndPlaceholder + { return } - wordToTranslate = (commandBar.text!.substring( - with: translatePrompt.count ..< ((commandBar.text!.count) - 1)) - ) + + if let commandBarText = commandBar.text { + let startIndex = commandBarText.index(commandBarText.startIndex, offsetBy: translatePrompt.count) + let endIndex = commandBarText.index(commandBarText.endIndex, offsetBy: -1) + wordToTranslate = String(commandBarText[startIndex ..< endIndex]) + } wordToTranslate = String(wordToTranslate.trailingSpacesTrimmed) // Check to see if the input was uppercase to return an uppercase conjugation. inputWordIsCapitalized = wordToTranslate.substring(toIdx: 1).isUppercase + wordToTranslate = wordToTranslate.lowercased() - wordToReturn = LanguageDBManager.shared.queryTranslation(of: wordToTranslate)[0] + let query = "SELECT * FROM translations WHERE word = ?" + let args = [wordToTranslate] + let outputCols = ["translation"] + wordToReturn = queryDBRow(query: query, outputCols: outputCols, args: args)[0] - if wordToReturn != "" { - if inputWordIsCapitalized { - proxy.insertText(wordToReturn.capitalized + " ") - } else { - proxy.insertText(wordToReturn + " ") - } - } else { + guard !wordToReturn.isEmpty else { commandState = .invalid + return + } + + if inputWordIsCapitalized { + proxy.insertText(wordToReturn.capitalized + " ") + } else { + proxy.insertText(wordToReturn + " ") } } diff --git a/Scribe.xcodeproj/project.pbxproj b/Scribe.xcodeproj/project.pbxproj index f09b43f5..08bc6a6f 100644 --- a/Scribe.xcodeproj/project.pbxproj +++ b/Scribe.xcodeproj/project.pbxproj @@ -386,6 +386,18 @@ D190B2582742525C00705659 /* Keyboard.xib in Resources */ = {isa = PBXBuildFile; fileRef = D1C0ACD92719E0AA001E11C3 /* Keyboard.xib */; }; D190B26827426ACD00705659 /* KeyboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D190B2592742565500705659 /* KeyboardViewController.swift */; }; D190B26927426ACD00705659 /* KeyboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D190B2592742565500705659 /* KeyboardViewController.swift */; }; + D19569E12BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569E22BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569E32BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569E42BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569E52BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569E62BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569E72BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569E82BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569E92BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569EA2BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569EB2BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; + D19569EC2BBCCAD700D025D7 /* LoadData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19569E02BBCCAD700D025D7 /* LoadData.swift */; }; D196B360279A051000228F3F /* ENPrivacyPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D196B35F279A051000228F3F /* ENPrivacyPolicy.swift */; }; D1A2DCB127AD37BD0057A10D /* ENAppText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A2DCB027AD37BD0057A10D /* ENAppText.swift */; }; D1A2DCB427AD3EB50057A10D /* AppUISymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A2DCB327AD3EB50057A10D /* AppUISymbols.swift */; }; @@ -937,6 +949,7 @@ D190B2592742565500705659 /* KeyboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardViewController.swift; sourceTree = ""; }; D190B28F27426F4900705659 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = SOURCE_ROOT; }; D190B29027426F4900705659 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; + D19569E02BBCCAD700D025D7 /* LoadData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadData.swift; sourceTree = ""; }; D196B35F279A051000228F3F /* ENPrivacyPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ENPrivacyPolicy.swift; sourceTree = ""; }; D1A2DCB027AD37BD0057A10D /* ENAppText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ENAppText.swift; sourceTree = ""; }; D1A2DCB327AD3EB50057A10D /* AppUISymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUISymbols.swift; sourceTree = ""; }; @@ -1364,6 +1377,7 @@ D171942E27AEDE110038660B /* KeyboardKeys.swift */, D190B2592742565500705659 /* KeyboardViewController.swift */, D190B24D2741B61000705659 /* LanguageDBManager.swift */, + D19569E02BBCCAD700D025D7 /* LoadData.swift */, D16DD3A429E78A1500FB9022 /* Utilities.swift */, D1C0ACD92719E0AA001E11C3 /* Keyboard.xib */, ); @@ -1988,6 +2002,7 @@ D17193F827AECC930038660B /* FRCommandVariables.swift in Sources */, D171945827AF237C0038660B /* ScribeKey.swift in Sources */, 1406B78C2A3209CF001DF45B /* AppExtensions.swift in Sources */, + D19569E12BBCCAD700D025D7 /* LoadData.swift in Sources */, D1B81D4A27BBBA200085FE5E /* ITCommandVariables.swift in Sources */, D180EC0328FDFABF0018E29B /* FR-AZERTYInterfaceVariables.swift in Sources */, D1CDED7B2A859FBF00098546 /* ENInterfaceVariables.swift in Sources */, @@ -2058,6 +2073,7 @@ 38BD214F22D592CA00C6795D /* DEKeyboardViewController.swift in Sources */, D171943A27AEF0560038660B /* KeyboardStyling.swift in Sources */, CE1378C628F5D7AC00E1CBC2 /* ScribeColor.swift in Sources */, + D19569E52BBCCAD700D025D7 /* LoadData.swift in Sources */, 30453972293B9E04003AE55B /* ToolTipViewTheme.swift in Sources */, 3045395B293B911C003AE55B /* AppTextStyling.swift in Sources */, D1B81D5527BBBA360085FE5E /* ITInterfaceVariables.swift in Sources */, @@ -2119,6 +2135,7 @@ D171942127AECD170038660B /* SVCommandVariables.swift in Sources */, D171943927AEF0560038660B /* KeyboardStyling.swift in Sources */, CE1378C528F5D7AC00E1CBC2 /* ScribeColor.swift in Sources */, + D19569E42BBCCAD700D025D7 /* LoadData.swift in Sources */, 30453970293B9DFF003AE55B /* ToolTipViewTheme.swift in Sources */, 30453959293B911B003AE55B /* AppTextStyling.swift in Sources */, D1B81D5427BBBA360085FE5E /* ITInterfaceVariables.swift in Sources */, @@ -2180,6 +2197,7 @@ D171942327AECD170038660B /* SVCommandVariables.swift in Sources */, D171943B27AEF0560038660B /* KeyboardStyling.swift in Sources */, CE1378C828F5D7AC00E1CBC2 /* ScribeColor.swift in Sources */, + D19569E92BBCCAD700D025D7 /* LoadData.swift in Sources */, 30453974293B9E05003AE55B /* ToolTipViewTheme.swift in Sources */, 3045395D293B911D003AE55B /* AppTextStyling.swift in Sources */, D1B81D5727BBBA360085FE5E /* ITInterfaceVariables.swift in Sources */, @@ -2241,6 +2259,7 @@ D171942527AECD170038660B /* SVCommandVariables.swift in Sources */, D171943D27AEF0560038660B /* KeyboardStyling.swift in Sources */, CE1378CA28F5D7AC00E1CBC2 /* ScribeColor.swift in Sources */, + D19569EB2BBCCAD700D025D7 /* LoadData.swift in Sources */, 30453976293B9E06003AE55B /* ToolTipViewTheme.swift in Sources */, 3045395F293B911E003AE55B /* AppTextStyling.swift in Sources */, D1B81D5927BBBA360085FE5E /* ITInterfaceVariables.swift in Sources */, @@ -2302,6 +2321,7 @@ D171942427AECD170038660B /* SVCommandVariables.swift in Sources */, D171943C27AEF0560038660B /* KeyboardStyling.swift in Sources */, CE1378C928F5D7AC00E1CBC2 /* ScribeColor.swift in Sources */, + D19569EA2BBCCAD700D025D7 /* LoadData.swift in Sources */, 30453975293B9E06003AE55B /* ToolTipViewTheme.swift in Sources */, 3045395E293B911E003AE55B /* AppTextStyling.swift in Sources */, D1B81D5827BBBA360085FE5E /* ITInterfaceVariables.swift in Sources */, @@ -2363,6 +2383,7 @@ D171942627AECD170038660B /* SVCommandVariables.swift in Sources */, D171943E27AEF0560038660B /* KeyboardStyling.swift in Sources */, CE1378CB28F5D7AC00E1CBC2 /* ScribeColor.swift in Sources */, + D19569EC2BBCCAD700D025D7 /* LoadData.swift in Sources */, 30453977293B9E06003AE55B /* ToolTipViewTheme.swift in Sources */, 30453960293B911F003AE55B /* AppTextStyling.swift in Sources */, D1B81D5A27BBBA360085FE5E /* ITInterfaceVariables.swift in Sources */, @@ -2424,6 +2445,7 @@ D1AB5B2829C757A100CCB0C1 /* ESInterfaceVariables.swift in Sources */, D1AB5B2929C757A100CCB0C1 /* SVInterfaceVariables.swift in Sources */, D1AB5B2A29C757A100CCB0C1 /* ScribeColor.swift in Sources */, + D19569E82BBCCAD700D025D7 /* LoadData.swift in Sources */, D1AB5B2B29C757A100CCB0C1 /* ToolTipViewTheme.swift in Sources */, D1AB5B2C29C757A100CCB0C1 /* AppTextStyling.swift in Sources */, D1AB5B2D29C757A100CCB0C1 /* ITInterfaceVariables.swift in Sources */, @@ -2485,6 +2507,7 @@ D1AFDF0629CA66D00033BF27 /* ESInterfaceVariables.swift in Sources */, D1AFDF0729CA66D00033BF27 /* SVInterfaceVariables.swift in Sources */, D1AFDF0829CA66D00033BF27 /* ScribeColor.swift in Sources */, + D19569E32BBCCAD700D025D7 /* LoadData.swift in Sources */, D1AFDF0929CA66D00033BF27 /* ToolTipViewTheme.swift in Sources */, D1AFDF0A29CA66D00033BF27 /* AppTextStyling.swift in Sources */, D1AFDF0B29CA66D00033BF27 /* ITInterfaceVariables.swift in Sources */, @@ -2546,6 +2569,7 @@ D1AFDF8329CA66F40033BF27 /* ESInterfaceVariables.swift in Sources */, D1AFDF8429CA66F40033BF27 /* SVInterfaceVariables.swift in Sources */, D1AFDF8529CA66F40033BF27 /* ScribeColor.swift in Sources */, + D19569E22BBCCAD700D025D7 /* LoadData.swift in Sources */, D1AFDF8629CA66F40033BF27 /* ToolTipViewTheme.swift in Sources */, D1AFDF8729CA66F40033BF27 /* AppTextStyling.swift in Sources */, D1AFDF8829CA66F40033BF27 /* ITInterfaceVariables.swift in Sources */, @@ -2607,6 +2631,7 @@ D1AFDFD929CA6E900033BF27 /* ESInterfaceVariables.swift in Sources */, D1AFDFDA29CA6E900033BF27 /* SVInterfaceVariables.swift in Sources */, D1AFDFDB29CA6E900033BF27 /* ScribeColor.swift in Sources */, + D19569E62BBCCAD700D025D7 /* LoadData.swift in Sources */, D1AFDFDC29CA6E900033BF27 /* ToolTipViewTheme.swift in Sources */, D1AFDFDD29CA6E900033BF27 /* AppTextStyling.swift in Sources */, D1AFDFDE29CA6E900033BF27 /* ITInterfaceVariables.swift in Sources */, @@ -2668,6 +2693,7 @@ D1B81D3327BBB6AD0085FE5E /* ESInterfaceVariables.swift in Sources */, D1B81D3527BBB6B50085FE5E /* SVInterfaceVariables.swift in Sources */, CE1378C728F5D7AC00E1CBC2 /* ScribeColor.swift in Sources */, + D19569E72BBCCAD700D025D7 /* LoadData.swift in Sources */, 30453973293B9E05003AE55B /* ToolTipViewTheme.swift in Sources */, 3045395C293B911D003AE55B /* AppTextStyling.swift in Sources */, D1B81D5627BBBA360085FE5E /* ITInterfaceVariables.swift in Sources */,