Skip to content
1 change: 1 addition & 0 deletions Keyboards/KeyboardsBase/InterfaceVariables.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
96 changes: 93 additions & 3 deletions Keyboards/KeyboardsBase/KeyboardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,51 @@ class KeyboardViewController: UIInputViewController {
}
}

func getEmojiAutoSuggestionsPatternMatching(for word: String) {
let emojisToDisplay = LanguageDBManager.shared.queryEmojisPatternMatching(of: word.lowercased())

if !emojisToDisplay[0].isEmpty {
emojisToDisplayArray = [String]()
currentEmojiTriggerWord = ":" + word.lowercased()

if !emojisToDisplay[2].isEmpty && DeviceType.isPad {
for i in 0 ..< 3 {
emojisToDisplayArray.append(emojisToDisplay[i])
}
autoAction2Visible = false
emojisToShow = .three

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)
}
conditionallyHideEmojiDividers()
} else if !emojisToDisplay[1].isEmpty {
for i in 0 ..< 2 {
emojisToDisplayArray.append(emojisToDisplay[i])
}
autoAction2Visible = false
emojisToShow = .two

if UITraitCollection.current.userInterfaceStyle == .light {
phoneEmojiDivider.backgroundColor = specialKeyColor
} else if UITraitCollection.current.userInterfaceStyle == .dark {
phoneEmojiDivider.backgroundColor = UIColor(cgColor: commandBarPlaceholderColorCG)
}
conditionallyHideEmojiDividers()
} else {
emojisToDisplayArray.append(emojisToDisplay[0])

emojisToShow = .one
}
} else {
emojisToShow = .zero
}
}

/// Generates an array of the three autocomplete words.
func getAutocompletions() {
completionWords = [" ", " ", " "]
Expand Down Expand Up @@ -633,12 +678,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)
Expand Down Expand Up @@ -843,6 +890,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.
Expand Down Expand Up @@ -2330,6 +2387,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.bool(forKey: dictionaryKey)
} else {
return true // return the default value
}
}

// MARK: Button Actions

/// Triggers actions based on the press of a key.
Expand Down Expand Up @@ -2716,6 +2784,15 @@ class KeyboardViewController: UIInputViewController {
pastStringInTextProxy = ""
}

if commandState == .colonToEmoji {
if !colonSearchString.isEmpty {
colonSearchString.removeLast()
} else {
commandState = .idle
loadKeys()
}
}

handleDeleteButtonPressed()
autoCapAtStartOfProxy()

Expand Down Expand Up @@ -2870,7 +2947,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 {
Expand Down
41 changes: 41 additions & 0 deletions Keyboards/KeyboardsBase/LanguageDBManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,47 @@ 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 {
outputValues.append(val)
if outputValues.count == 3 { return }
}
}
}
}
} catch {}

while outputValues.count < 3 {
outputValues.append("")
}

return Array(outputValues.prefix(3))
}

/// Query the noun form of word in `nonuns`.
func queryNounForm(of word: String) -> [String] {
let query = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum EmojisToShow {
var emojisToShow: EmojisToShow = .zero
var currentEmojiTriggerWord = ""
var emojiAutoActionRepeatPossible = false
var colonSearchString = ""

var firstCompletionIsHighlighted = false
var spaceAutoInsertIsPossible = false
Expand Down
1 change: 1 addition & 0 deletions Scribe/ParentTableCellModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ enum UserInteractiveState {
case autosuggestEmojis
case toggleAccentCharacters
case toggleWordForWordDeletion
case colonToEmoji
case none
}

Expand Down
6 changes: 6 additions & 0 deletions Scribe/SettingsTab/SettingsTableData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ enum SettingsTableData {
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.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -199,6 +203,14 @@ final class InfoChildTableViewCell: UITableViewCell {
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
}
}
Expand Down
30 changes: 30 additions & 0 deletions Tests/Keyboards/KeyboardsBase/EmojiQueryTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-3.0-or-later

import Foundation
@testable import Scribe
import XCTest

class EmojiQueryTests: XCTestCase {

Check failure on line 8 in Tests/Keyboards/KeyboardsBase/EmojiQueryTests.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
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)

Check failure on line 14 in Tests/Keyboards/KeyboardsBase/EmojiQueryTests.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
XCTAssertEqual(results.count, 3, "Should always return 3 elements (including empty strings)")
}

Check failure on line 17 in Tests/Keyboards/KeyboardsBase/EmojiQueryTests.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
func testQueryEmojisPatternMatchingWithEmptyKeyword() {
let results = LanguageDBManager.shared.queryEmojisPatternMatching(of: "")
XCTAssertEqual(results.count, 3)
}

Check failure on line 22 in Tests/Keyboards/KeyboardsBase/EmojiQueryTests.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
func testQueryEmojisPatternMatchingWithNonExistentKeyword() {
let results = LanguageDBManager.shared.queryEmojisPatternMatching(of: "nonexistentkeyword12345")
XCTAssertEqual(results.count, 3)
XCTAssertEqual(results[0], "")
XCTAssertEqual(results[1], "")
XCTAssertEqual(results[2], "")
}
}
17 changes: 17 additions & 0 deletions Tests/Keyboards/KeyboardsBase/KeyboardCommandTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-3.0-or-later

import Foundation
@testable import Scribe
import XCTest

class KeyboardCommandTests: XCTestCase {

Check failure on line 8 in Tests/Keyboards/KeyboardsBase/KeyboardCommandTests.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
func testColonToEmojiIsEnabled() {
let keyboard = KeyboardViewController()
// Default should be true as per the implementation
XCTAssertTrue(keyboard.colonToEmojiIsEnabled())
}

Check failure on line 14 in Tests/Keyboards/KeyboardsBase/KeyboardCommandTests.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
// Note: Testing UI state transitions often requires a more complex setup
// with UIInputViewController and its proxy. Here we test the logic we can.
}
Loading