diff --git a/Keyboards/KeyboardsBase/InterfaceVariables.swift b/Keyboards/KeyboardsBase/InterfaceVariables.swift index cf34a93c..3903d907 100644 --- a/Keyboards/KeyboardsBase/InterfaceVariables.swift +++ b/Keyboards/KeyboardsBase/InterfaceVariables.swift @@ -96,6 +96,7 @@ enum CommandState { case alreadyPlural case invalid case displayInformation + case colonToEmoji } /// States of the keyboard corresponding to which auto actions should be presented. diff --git a/Keyboards/KeyboardsBase/KeyboardViewController.swift b/Keyboards/KeyboardsBase/KeyboardViewController.swift index 97bc06b9..ef3b0388 100644 --- a/Keyboards/KeyboardsBase/KeyboardViewController.swift +++ b/Keyboards/KeyboardsBase/KeyboardViewController.swift @@ -404,6 +404,53 @@ class KeyboardViewController: UIInputViewController { } } + func getEmojiAutoSuggestionsPatternMatching(for word: String) { + let emojisToDisplay = LanguageDBManager.shared.queryEmojisPatternMatching(of: word.lowercased()) + + emojisToDisplayArray = [String]() + if !emojisToDisplay[0].isEmpty { + currentEmojiTriggerWord = ":" + word.lowercased() + + for emoji in emojisToDisplay where !emoji.isEmpty { + emojisToDisplayArray.append(emoji) + } + + switch emojisToDisplayArray.count { + case 1: emojisToShow = .one + case 2: emojisToShow = .two + case 3: emojisToShow = .three + case 4: emojisToShow = .four + case 5: emojisToShow = .five + case 6: emojisToShow = .six + default: emojisToShow = .zero + } + + if commandState == .colonToEmoji { + autoAction0Visible = false + autoAction2Visible = false + } + + if DeviceType.isPad && emojisToShow.rawValue >= 2 { + if UITraitCollection.current.userInterfaceStyle == .light { + padEmojiDivider0.backgroundColor = specialKeyColor + padEmojiDivider1.backgroundColor = specialKeyColor + } else if UITraitCollection.current.userInterfaceStyle == .dark { + padEmojiDivider0.backgroundColor = UIColor(cgColor: commandBarPlaceholderColorCG) + padEmojiDivider1.backgroundColor = UIColor(cgColor: commandBarPlaceholderColorCG) + } + } else if DeviceType.isPhone && emojisToShow.rawValue >= 2 { + if UITraitCollection.current.userInterfaceStyle == .light { + phoneEmojiDivider.backgroundColor = specialKeyColor + } else if UITraitCollection.current.userInterfaceStyle == .dark { + phoneEmojiDivider.backgroundColor = UIColor(cgColor: commandBarPlaceholderColorCG) + } + } + conditionallyHideEmojiDividers() + } else { + emojisToShow = .zero + } + } + /// Generates an array of the three autocomplete words. func getAutocompletions() { completionWords = [" ", " ", " "] @@ -633,12 +680,14 @@ class KeyboardViewController: UIInputViewController { autoActionAnnotationSeparators.forEach { $0.removeFromSuperview() } autoActionAnnotationSeparators.removeAll() - if autoActionState == .suggest { + if commandState == .colonToEmoji { + getEmojiAutoSuggestionsPatternMatching(for: colonSearchString) + } else if autoActionState == .suggest { getAutosuggestions() } else { getAutocompletions() } - if commandState == .idle { + if [.idle, .colonToEmoji].contains(commandState) { deactivateBtn(btn: translateKey) deactivateBtn(btn: conjugateKey) deactivateBtn(btn: pluralKey) @@ -653,144 +702,168 @@ class KeyboardViewController: UIInputViewController { hideConjugateAndPluralKeys(state: false) } - if autoAction0Visible { - allowUndo = false - firstCompletionIsHighlighted = false - // Highlight if the current prefix is the first autocompletion. - if currentPrefix == completionWords[0] && completionWords[1] != " " { - firstCompletionIsHighlighted = true - } - setBtn( - btn: translateKey, - color: firstCompletionIsHighlighted ? keyColor.withAlphaComponent(0.5) : keyboardBgColor, - name: "AutoAction0", - canBeCapitalized: false, - isSpecial: false - ) - styleBtn( - btn: translateKey, - title: completionWords[0], - radius: firstCompletionIsHighlighted ? commandKeyCornerRadius / 2.5 : commandKeyCornerRadius - ) - if translateKey.currentTitle != " " { - activateBtn(btn: translateKey) + if commandState == .colonToEmoji && emojisToShow != .zero { + let emojiButtons: [UIButton] + if DeviceType.isPad { + emojiButtons = [translateKey, conjugateKey, pluralKey, padEmojiKey0, padEmojiKey1, padEmojiKey2] + } else { + emojiButtons = [translateKey, conjugateKey, pluralKey, phoneEmojiKey0, phoneEmojiKey1] } - autoActionAnnotation(autoActionWord: completionWords[0], index: 0, KVC: self) - } - // Add the current word being typed to the completion words if there is only one option that's highlighted. - if firstCompletionIsHighlighted && completionWords[1] == " " && completionWords[0] != currentPrefix { -// spaceAutoInsertIsPossible = true - completionWords[1] = currentPrefix - } + for (index, emoji) in emojisToDisplayArray.enumerated() { + if index < emojiButtons.count { + let btn = emojiButtons[index] + setBtn(btn: btn, color: keyboardBgColor, name: "EmojiKey\(index)", canBeCapitalized: false, isSpecial: false) + styleBtn(btn: btn, title: emoji, radius: commandKeyCornerRadius) + if DeviceType.isPhone { + btn.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPhone) + } else { + btn.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPad) + } + activateBtn(btn: btn) + } + } + conditionallyHideEmojiDividers() + } else { + if autoAction0Visible { + allowUndo = false + firstCompletionIsHighlighted = false + // Highlight if the current prefix is the first autocompletion. + if currentPrefix == completionWords[0] && completionWords[1] != " " { + firstCompletionIsHighlighted = true + } + setBtn( + btn: translateKey, + color: firstCompletionIsHighlighted ? keyColor.withAlphaComponent(0.5) : keyboardBgColor, + name: "AutoAction0", + canBeCapitalized: false, + isSpecial: false + ) + styleBtn( + btn: translateKey, + title: completionWords[0], + radius: firstCompletionIsHighlighted ? commandKeyCornerRadius / 2.5 : commandKeyCornerRadius + ) + if translateKey.currentTitle != " " { + activateBtn(btn: translateKey) + } + autoActionAnnotation(autoActionWord: completionWords[0], index: 0, KVC: self) + } - setBtn( - btn: conjugateKey, - color: keyboardBgColor, name: "AutoAction1", - canBeCapitalized: false, - isSpecial: false - ) - styleBtn( - btn: conjugateKey, - title: !autoAction0Visible ? completionWords[0] : completionWords[1], - radius: commandKeyCornerRadius - ) - if conjugateKey.currentTitle != " " { - activateBtn(btn: conjugateKey) - } - autoActionAnnotation( - autoActionWord: !autoAction0Visible ? completionWords[0] : completionWords[1], index: 1, KVC: self - ) + // Add the current word being typed to the completion words if there is only one option that's highlighted. + if firstCompletionIsHighlighted && completionWords[1] == " " && completionWords[0] != currentPrefix { + // spaceAutoInsertIsPossible = true + completionWords[1] = currentPrefix + } - if autoAction2Visible && emojisToShow == .zero { setBtn( - btn: pluralKey, - color: keyboardBgColor, - name: "AutoAction2", + btn: conjugateKey, + color: keyboardBgColor, name: "AutoAction1", canBeCapitalized: false, isSpecial: false ) styleBtn( - btn: pluralKey, - title: !autoAction0Visible ? completionWords[1] : completionWords[2], + btn: conjugateKey, + title: !autoAction0Visible ? completionWords[0] : completionWords[1], radius: commandKeyCornerRadius ) - if pluralKey.currentTitle != " " { - activateBtn(btn: pluralKey) + if conjugateKey.currentTitle != " " { + activateBtn(btn: conjugateKey) } autoActionAnnotation( - autoActionWord: !autoAction0Visible ? completionWords[1] : completionWords[2], index: 2, KVC: self + autoActionWord: !autoAction0Visible ? completionWords[0] : completionWords[1], index: 1, KVC: self ) - conditionallyHideEmojiDividers() - } else if autoAction2Visible && emojisToShow == .one { - setBtn( - btn: pluralKey, - color: keyboardBgColor, - name: "AutoAction2", - canBeCapitalized: false, - isSpecial: false - ) - styleBtn( - btn: pluralKey, - title: emojisToDisplayArray[0], - radius: commandKeyCornerRadius - ) - if DeviceType.isPhone { - pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPhone) - } else if DeviceType.isPad { - pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPad) - } - activateBtn(btn: pluralKey) - - conditionallyHideEmojiDividers() - } else if !autoAction2Visible && emojisToShow == .two { - setBtn( - btn: phoneEmojiKey0, - color: keyboardBgColor, - name: "EmojiKey0", - canBeCapitalized: false, - isSpecial: false - ) - setBtn( - btn: phoneEmojiKey1, - color: keyboardBgColor, - name: "EmojiKey1", - canBeCapitalized: false, - isSpecial: false - ) - styleBtn(btn: phoneEmojiKey0, title: emojisToDisplayArray[0], radius: commandKeyCornerRadius) - styleBtn(btn: phoneEmojiKey1, title: emojisToDisplayArray[1], radius: commandKeyCornerRadius) + if autoAction2Visible && emojisToShow == .zero { + setBtn( + btn: pluralKey, + color: keyboardBgColor, + name: "AutoAction2", + canBeCapitalized: false, + isSpecial: false + ) + styleBtn( + btn: pluralKey, + title: !autoAction0Visible ? completionWords[1] : completionWords[2], + radius: commandKeyCornerRadius + ) + if pluralKey.currentTitle != " " { + activateBtn(btn: pluralKey) + } + autoActionAnnotation( + autoActionWord: !autoAction0Visible ? completionWords[1] : completionWords[2], index: 2, KVC: self + ) - if DeviceType.isPhone { - 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 * scalarFontPad) - phoneEmojiKey1.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPad) - } + conditionallyHideEmojiDividers() + } else if autoAction2Visible && emojisToShow == .one { + setBtn( + btn: pluralKey, + color: keyboardBgColor, + name: "AutoAction2", + canBeCapitalized: false, + isSpecial: false + ) + styleBtn( + btn: pluralKey, + title: emojisToDisplayArray[0], + radius: commandKeyCornerRadius + ) + if DeviceType.isPhone { + pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPhone) + } else if DeviceType.isPad { + pluralKey.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPad) + } + activateBtn(btn: pluralKey) - activateBtn(btn: phoneEmojiKey0) - activateBtn(btn: phoneEmojiKey1) + conditionallyHideEmojiDividers() + } else if !autoAction2Visible && emojisToShow.rawValue >= 2 { + if DeviceType.isPhone || emojisToShow == .two { + setBtn( + btn: phoneEmojiKey0, + color: keyboardBgColor, + name: "EmojiKey0", + canBeCapitalized: false, + isSpecial: false + ) + setBtn( + btn: phoneEmojiKey1, + color: keyboardBgColor, + name: "EmojiKey1", + canBeCapitalized: false, + isSpecial: false + ) + styleBtn(btn: phoneEmojiKey0, title: emojisToDisplayArray[0], radius: commandKeyCornerRadius) + styleBtn(btn: phoneEmojiKey1, title: emojisToDisplayArray[1], radius: commandKeyCornerRadius) + + if DeviceType.isPhone { + 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 * scalarFontPad) + phoneEmojiKey1.titleLabel?.font = .systemFont(ofSize: scribeKey.frame.height * scalarFontPad) + } - conditionallyHideEmojiDividers() - } else if !autoAction2Visible && emojisToShow == .three { - setBtn(btn: padEmojiKey0, color: keyboardBgColor, name: "EmojiKey0", canBeCapitalized: false, isSpecial: false) - setBtn(btn: padEmojiKey1, color: keyboardBgColor, name: "EmojiKey1", canBeCapitalized: false, isSpecial: false) - setBtn(btn: padEmojiKey2, color: keyboardBgColor, name: "EmojiKey2", canBeCapitalized: false, isSpecial: false) - styleBtn(btn: padEmojiKey0, title: emojisToDisplayArray[0], radius: commandKeyCornerRadius) - styleBtn(btn: padEmojiKey1, title: emojisToDisplayArray[1], radius: commandKeyCornerRadius) - styleBtn(btn: padEmojiKey2, title: emojisToDisplayArray[2], radius: commandKeyCornerRadius) - - 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) - activateBtn(btn: padEmojiKey2) + activateBtn(btn: phoneEmojiKey0) + activateBtn(btn: phoneEmojiKey1) + } else if DeviceType.isPad && emojisToShow.rawValue >= 3 { + setBtn(btn: padEmojiKey0, color: keyboardBgColor, name: "EmojiKey0", canBeCapitalized: false, isSpecial: false) + setBtn(btn: padEmojiKey1, color: keyboardBgColor, name: "EmojiKey1", canBeCapitalized: false, isSpecial: false) + setBtn(btn: padEmojiKey2, color: keyboardBgColor, name: "EmojiKey2", canBeCapitalized: false, isSpecial: false) + styleBtn(btn: padEmojiKey0, title: emojisToDisplayArray[0], radius: commandKeyCornerRadius) + styleBtn(btn: padEmojiKey1, title: emojisToDisplayArray[1], radius: commandKeyCornerRadius) + styleBtn(btn: padEmojiKey2, title: emojisToDisplayArray[2], radius: commandKeyCornerRadius) + + 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) + activateBtn(btn: padEmojiKey2) + } - conditionallyHideEmojiDividers() + conditionallyHideEmojiDividers() + } } translateKey.layer.shadowColor = UIColor.clear.cgColor @@ -843,6 +916,16 @@ class KeyboardViewController: UIInputViewController { allowUndo = false } + if commandState == .colonToEmoji { + for _ in 0 ... colonSearchString.count { + proxy.deleteBackward() + } + proxy.insertText(keyPressed.titleLabel?.text ?? "") + commandState = .idle + loadKeys() + return + } + clearPrefixFromTextFieldProxy() emojisToDisplayArray = [String]() // Remove the space from the previous auto action or replace the current prefix. @@ -2301,7 +2384,7 @@ class KeyboardViewController: UIInputViewController { if let userDefaults = UserDefaults(suiteName: "group.be.scri.userDefaultsContainer") { let dictionaryKey = langCode + "DoubleSpacePeriods" - return userDefaults.bool(forKey: dictionaryKey) + return userDefaults.object(forKey: dictionaryKey) as? Bool ?? true } else { return true // return the default value } @@ -2312,7 +2395,7 @@ class KeyboardViewController: UIInputViewController { if let userDefaults = UserDefaults(suiteName: "group.be.scri.userDefaultsContainer") { let dictionaryKey = langCode + "EmojiAutosuggest" - return userDefaults.bool(forKey: dictionaryKey) + return userDefaults.object(forKey: dictionaryKey) as? Bool ?? true } else { return true // return the default value } @@ -2330,6 +2413,17 @@ class KeyboardViewController: UIInputViewController { } + func colonToEmojiIsEnabled() -> Bool { + let langCode = languagesAbbrDict[controllerLanguage] ?? "unknown" + if let userDefaults = UserDefaults(suiteName: "group.be.scri.userDefaultsContainer") { + let dictionaryKey = langCode + "ColonToEmoji" + + return userDefaults.object(forKey: dictionaryKey) as? Bool ?? true + } else { + return true // return the default value + } + } + // MARK: Button Actions /// Triggers actions based on the press of a key. @@ -2630,30 +2724,8 @@ class KeyboardViewController: UIInputViewController { loadKeys() } - case "EmojiKey0": - if DeviceType.isPhone || emojisToShow == .two { - executeAutoAction(keyPressed: phoneEmojiKey0) - } else if DeviceType.isPad { - executeAutoAction(keyPressed: padEmojiKey0) - } - if shiftButtonState == .normal { - shiftButtonState = .shift - } - loadKeys() - - case "EmojiKey1": - if DeviceType.isPhone || emojisToShow == .two { - executeAutoAction(keyPressed: phoneEmojiKey1) - } else if DeviceType.isPad { - executeAutoAction(keyPressed: padEmojiKey1) - } - if shiftButtonState == .normal { - shiftButtonState = .shift - } - loadKeys() - - case "EmojiKey2": - executeAutoAction(keyPressed: padEmojiKey2) + case "EmojiKey0", "EmojiKey1", "EmojiKey2", "EmojiKey3", "EmojiKey4", "EmojiKey5": + executeAutoAction(keyPressed: sender) if shiftButtonState == .normal { shiftButtonState = .shift } @@ -2716,6 +2788,15 @@ class KeyboardViewController: UIInputViewController { pastStringInTextProxy = "" } + if commandState == .colonToEmoji { + if !colonSearchString.isEmpty { + colonSearchString.removeLast() + } else { + commandState = .idle + loadKeys() + } + } + handleDeleteButtonPressed() autoCapAtStartOfProxy() @@ -2870,7 +2951,20 @@ class KeyboardViewController: UIInputViewController { shiftButtonState = .normal loadKeys() } - if [.idle, .selectCommand, .alreadyPlural, .invalid].contains(commandState) { + + if keyToDisplay == ":" && commandState == .idle && colonToEmojiIsEnabled() { + commandState = .colonToEmoji + colonSearchString = "" + } else if commandState == .colonToEmoji { + if keyToDisplay.rangeOfCharacter(from: CharacterSet.alphanumerics) != nil { + colonSearchString += keyToDisplay + } else { + commandState = .idle + loadKeys() + } + } + + if [.idle, .selectCommand, .alreadyPlural, .invalid, .colonToEmoji].contains(commandState) { proxy.insertText(keyToDisplay) } else { if let currentText = commandBar.text { @@ -2887,7 +2981,7 @@ class KeyboardViewController: UIInputViewController { // Reset emoji repeat functionality. if !( - ["EmojiKey0", "EmojiKey1", "EmojiKey2"].contains(originalKey) + ["EmojiKey0", "EmojiKey1", "EmojiKey2", "EmojiKey3", "EmojiKey4", "EmojiKey5"].contains(originalKey) || (originalKey == "AutoAction2" && emojisToShow == .one) ) { emojiAutoActionRepeatPossible = false diff --git a/Keyboards/KeyboardsBase/LanguageDBManager.swift b/Keyboards/KeyboardsBase/LanguageDBManager.swift index ac49e68f..cf253b0c 100644 --- a/Keyboards/KeyboardsBase/LanguageDBManager.swift +++ b/Keyboards/KeyboardsBase/LanguageDBManager.swift @@ -268,6 +268,49 @@ extension LanguageDBManager { return queryDBRow(query: query, outputCols: outputCols, args: StatementArguments(args)) } + /// Query emojis of word in `emoji_keywords` using pattern matching. + func queryEmojisPatternMatching(of word: String) -> [String] { + var outputValues = [String]() + let query = """ + SELECT + emoji_keyword_0, emoji_keyword_1, emoji_keyword_2 + + FROM + emoji_keywords + + WHERE + word LIKE ? + + ORDER BY + LENGTH(word) ASC + + LIMIT + 3 + """ + let args = StatementArguments(["\(word.lowercased())%"]) + do { + try database?.read { db in + let rows = try Row.fetchAll(db, sql: query, arguments: args) + for row in rows { + for col in ["emoji_keyword_0", "emoji_keyword_1", "emoji_keyword_2"] { + if let val = row[col] as? String, !val.isEmpty { + if !outputValues.contains(val) { + outputValues.append(val) + } + if outputValues.count == 6 { return } + } + } + } + } + } catch {} + + while outputValues.count < 6 { + outputValues.append("") + } + + return Array(outputValues.prefix(6)) + } + /// Query the noun form of word in `nonuns`. func queryNounForm(of word: String) -> [String] { let query = """ diff --git a/Keyboards/KeyboardsBase/ScribeFunctionality/CommandVariables.swift b/Keyboards/KeyboardsBase/ScribeFunctionality/CommandVariables.swift index 7ae6d22d..bff57fa4 100644 --- a/Keyboards/KeyboardsBase/ScribeFunctionality/CommandVariables.swift +++ b/Keyboards/KeyboardsBase/ScribeFunctionality/CommandVariables.swift @@ -14,16 +14,20 @@ var autoAction0Visible = true var autoAction2Visible = true /// States of the emoji display corresponding to the number to show. -enum EmojisToShow { - case zero - case one - case two - case three +enum EmojisToShow: Int { + case zero = 0 + case one = 1 + case two = 2 + case three = 3 + case four = 4 + case five = 5 + case six = 6 } var emojisToShow: EmojisToShow = .zero var currentEmojiTriggerWord = "" var emojiAutoActionRepeatPossible = false +var colonSearchString = "" var firstCompletionIsHighlighted = false var spaceAutoInsertIsPossible = false diff --git a/Scribe/ParentTableCellModel.swift b/Scribe/ParentTableCellModel.swift index 58d21d56..94a372da 100644 --- a/Scribe/ParentTableCellModel.swift +++ b/Scribe/ParentTableCellModel.swift @@ -70,6 +70,7 @@ enum UserInteractiveState { case autosuggestEmojis case toggleAccentCharacters case toggleWordForWordDeletion + case colonToEmoji case none } diff --git a/Scribe/SettingsTab/SettingsTableData.swift b/Scribe/SettingsTab/SettingsTableData.swift index 44ee9fe6..ff2b76fc 100644 --- a/Scribe/SettingsTab/SettingsTableData.swift +++ b/Scribe/SettingsTab/SettingsTableData.swift @@ -73,12 +73,34 @@ enum SettingsTableData { sectionState: .none(.autosuggestEmojis), shortDescription: NSLocalizedString("i18n.app.settings.keyboard.functionality.auto_suggest_emoji_description", value: "Turn on emoji suggestions and completions for more expressive typing.", comment: "") ), - Section( - sectionTitle: NSLocalizedString("i18n.app.word_for_word", value: "Word for word deletion on long press", comment: ""), - hasToggle: true, - sectionState: .none(.toggleWordForWordDeletion), - shortDescription: NSLocalizedString("i18n.app.word_for_word.description", value: "Word for word deletion.", comment: "") - ) + Section( + sectionTitle: NSLocalizedString( + "i18n.app.settings.keyboard.functionality.delete_word_by_word", + value: "Word for word deletion on long press", + comment: "" + ), + hasToggle: true, + sectionState: .none(.toggleWordForWordDeletion), + shortDescription: NSLocalizedString( + "i18n.app.settings.keyboard.functionality.delete_word_by_word_description", + value: "Delete text word by word when the delete key is pressed and held.", + comment: "" + ) +), + Section( + sectionTitle: NSLocalizedString( + "i18n.app.settings.keyboard.functionality.colon_to_emoji", + value: "Colon to emoji entry", + comment: "" + ), + hasToggle: true, + sectionState: .none(.colonToEmoji), + shortDescription: NSLocalizedString( + "i18n.app.settings.keyboard.functionality.colon_to_emoji_description", + value: "Type : followed by a keyword to suggest emojis.", + comment: "" + ) + ) ], hasDynamicData: nil ) diff --git a/Scribe/Views/Cells/InfoChildTableViewCell/InfoChildTableViewCell.swift b/Scribe/Views/Cells/InfoChildTableViewCell/InfoChildTableViewCell.swift index a27303fd..fed8e652 100644 --- a/Scribe/Views/Cells/InfoChildTableViewCell/InfoChildTableViewCell.swift +++ b/Scribe/Views/Cells/InfoChildTableViewCell/InfoChildTableViewCell.swift @@ -151,6 +151,10 @@ final class InfoChildTableViewCell: UITableViewCell { let dictionaryKey = languageCode + "WordForWordDeletion" userDefaults.setValue(toggleSwitch.isOn, forKey: dictionaryKey) + case .colonToEmoji: + let dictionaryKey = languageCode + "ColonToEmoji" + userDefaults.setValue(toggleSwitch.isOn, forKey: dictionaryKey) + case .none: break } @@ -164,7 +168,7 @@ final class InfoChildTableViewCell: UITableViewCell { if let toggleValue = userDefaults.object(forKey: dictionaryKey) as? Bool { toggleSwitch.isOn = toggleValue } else { - toggleSwitch.isOn = false // Default value + toggleSwitch.isOn = false // default value } case .toggleAccentCharacters: @@ -172,7 +176,7 @@ final class InfoChildTableViewCell: UITableViewCell { if let toggleValue = userDefaults.object(forKey: dictionaryKey) as? Bool { toggleSwitch.isOn = toggleValue } else { - toggleSwitch.isOn = false // Default value + toggleSwitch.isOn = false // default value } case .doubleSpacePeriods: @@ -180,7 +184,7 @@ final class InfoChildTableViewCell: UITableViewCell { if let toggleValue = userDefaults.object(forKey: dictionaryKey) as? Bool { toggleSwitch.isOn = toggleValue } else { - toggleSwitch.isOn = true // Default value + toggleSwitch.isOn = true // default value } case .autosuggestEmojis: @@ -188,7 +192,7 @@ final class InfoChildTableViewCell: UITableViewCell { if let toggleValue = userDefaults.object(forKey: dictionaryKey) as? Bool { toggleSwitch.isOn = toggleValue } else { - toggleSwitch.isOn = true // Default value + toggleSwitch.isOn = true // default value } case .toggleWordForWordDeletion: @@ -196,7 +200,15 @@ final class InfoChildTableViewCell: UITableViewCell { if let toggleValue = userDefaults.object(forKey: dictionaryKey) as? Bool { toggleSwitch.isOn = toggleValue } else { - toggleSwitch.isOn = false // Default value + toggleSwitch.isOn = false // default value + } + + case .colonToEmoji: + let dictionaryKey = languageCode + "ColonToEmoji" + if let toggleValue = userDefaults.object(forKey: dictionaryKey) as? Bool { + toggleSwitch.isOn = toggleValue + } else { + toggleSwitch.isOn = true // default value } case .none: break diff --git a/Tests/Keyboards/KeyboardsBase/EmojiQueryTests.swift b/Tests/Keyboards/KeyboardsBase/EmojiQueryTests.swift new file mode 100644 index 00000000..59ccc5fb --- /dev/null +++ b/Tests/Keyboards/KeyboardsBase/EmojiQueryTests.swift @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +@testable import Scribe +import XCTest + +class EmojiQueryTests: XCTestCase { + + func testQueryEmojisPatternMatchingWithCommonKeyword() { + // This test assumes the database is populated with some emojis. + // If not, it might return empty, which we also handle. + let keyword = "happ" + let results = LanguageDBManager.shared.queryEmojisPatternMatching(of: keyword) + + XCTAssertEqual(results.count, 6, "Should always return 6 elements (including empty strings)") + } + + func testQueryEmojisPatternMatchingWithEmptyKeyword() { + let results = LanguageDBManager.shared.queryEmojisPatternMatching(of: "") + XCTAssertEqual(results.count, 6) + } + + func testQueryEmojisPatternMatchingWithNonExistentKeyword() { + let results = LanguageDBManager.shared.queryEmojisPatternMatching(of: "nonexistentkeyword12345") + XCTAssertEqual(results.count, 6) + XCTAssertEqual(results[0], "") + XCTAssertEqual(results[1], "") + XCTAssertEqual(results[2], "") + XCTAssertEqual(results[3], "") + XCTAssertEqual(results[4], "") + XCTAssertEqual(results[5], "") + } +} diff --git a/Tests/Keyboards/KeyboardsBase/KeyboardCommandTests.swift b/Tests/Keyboards/KeyboardsBase/KeyboardCommandTests.swift new file mode 100644 index 00000000..7cc89d1c --- /dev/null +++ b/Tests/Keyboards/KeyboardsBase/KeyboardCommandTests.swift @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +@testable import Scribe +import XCTest + +class KeyboardCommandTests: XCTestCase { + + func testColonToEmojiIsEnabled() { + let keyboard = KeyboardViewController() + // Default should be true as per the implementation. + XCTAssertTrue(keyboard.colonToEmojiIsEnabled()) + } +}