Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7648193
Add colon-to-emoji toggle feature to settings and keyboard
jatinfoujdar Jan 8, 2026
d20a1cf
Add colon-to-emoji toggle feature
jatinfoujdar Jan 8, 2026
bdebaeb
Merge branch 'main' into feature/colon-to-emoji-toggle
andrewtavis Jan 11, 2026
655bb6b
style: fix swiftlint trailing whitespace violations in tests
jatinfoujdar Jan 11, 2026
ad14475
Toggle colon to emoji
jatinfoujdar Jan 11, 2026
c5025ad
Minor update of comments for code
andrewtavis Jan 11, 2026
a2bd368
fix: clean up emoji suggestion UI to distinguish from word suggestions
jatinfoujdar Jan 13, 2026
4359e6e
Populate leftmost emoji button with first suggestion and expand database
jatinfoujdar Jan 13, 2026
5a25107
chore: refresh mergeability with main
jatinfoujdar Jan 14, 2026
0199e6b
Resolve merge conflict in SettingsTableData
jatinfoujdar Jan 14, 2026
c853d54
Merge branch 'upstream/main' into feature/colon-to-emoji-toggle
jatinfoujdar Feb 7, 2026
6f78482
Expand emoji suggestions to 6 on phones and 9 on tablets, resolved me…
jatinfoujdar Feb 7, 2026
2654868
Merge upstream/main into feature/colon-to-emoji-toggle and resolve co…
jatinfoujdar Feb 25, 2026
74328c8
Minor fixes of using where instead of for-if
andrewtavis Feb 25, 2026
ac10d6a
Update pre-commit directions to use prek
andrewtavis Feb 25, 2026
d2359d5
Minor file formatting
andrewtavis Feb 25, 2026
ea308e4
fix build failure braces mismatch
catreedle Feb 26, 2026
8cc870b
fix: resolve braces mismatch in KeyboardViewController
jatinfoujdar Feb 26, 2026
1f0729f
fix: resolve optional decay build errors and update gitignore
jatinfoujdar Feb 26, 2026
7a4f19b
Merge branch 'catreedle/colon-to-emoji' into feature/colon-to-emoji-t…
jatinfoujdar Feb 26, 2026
2e43787
Fix build failures, enable missing tests, and resolve DB manager cras…
jatinfoujdar Feb 28, 2026
46f68aa
Fix swiftlint errors
andrewtavis Feb 28, 2026
e410794
Minor fix to comments
andrewtavis Feb 28, 2026
7c11e59
Fix keyboard test: remove NIB view load, make btn params optional
jatinfoujdar Mar 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ DerivedData/

# Testing
code_coverage.json

# Python virtual environment
venv/
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Setting up a Python environment and pre-commit.
# Set up a Python environment and prek for pre-commit hooks.

# Unix or MacOS:
# >>> python3 -m venv venv
Expand All @@ -9,9 +9,9 @@
# >>> venv\Scripts\activate.bat

# >>> pip install --upgrade pip
# >>> pip install pre-commit
# >>> pre-commit install
# >>> pre-commit run --all-files
# >>> pip install prek
# >>> prek install
# >>> prek run --all-files

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
84 changes: 73 additions & 11 deletions Keyboards/DataManager/LanguageDBManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,42 @@ class LanguageDBManager {

/// Makes a connection to the language database given the value for controllerLanguage.
private func openDBQueue(_ dbName: String) -> DatabaseQueue {
let dbResourcePath = Bundle.main.path(forResource: dbName, ofType: "sqlite")!
let mainBundlePath = Bundle.main.path(forResource: dbName, ofType: "sqlite")
let classBundlePath = Bundle(for: LanguageDBManager.self).path(forResource: dbName, ofType: "sqlite")

guard let resourcePath = mainBundlePath ?? classBundlePath else {
print("Database \(dbName).sqlite not found in main or class bundle. Using in-memory DB.")
return try! DatabaseQueue()
}

let fileManager = FileManager.default
do {
let dbPath = try fileManager
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("\(dbName).sqlite")
.path
let appSupportURL = try fileManager.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let dbURL = appSupportURL.appendingPathComponent("\(dbName).sqlite")
let dbPath = dbURL.path

var shouldCopy = true
if fileManager.fileExists(atPath: dbPath) {
// Only copy if the resource is newer or if we want to ensure a fresh copy.
// For now, keeping the "fresh copy" behavior but more safely.
try fileManager.removeItem(atPath: dbPath)
}
try fileManager.copyItem(atPath: dbResourcePath, toPath: dbPath)
let dbQueue = try DatabaseQueue(path: dbPath)
return dbQueue

if shouldCopy {
try fileManager.copyItem(atPath: resourcePath, toPath: dbPath)
}

return try DatabaseQueue(path: dbPath)
} catch {
print("An error occurred: UILexicon not available")
let dbQueue = try! DatabaseQueue(path: dbResourcePath)
return dbQueue
print("An error occurred during DB setup for \(dbName): \(error). Attempting read-only access.")
var config = Configuration()
config.readonly = true
if let dbQueue = try? DatabaseQueue(path: resourcePath, configuration: config) {
return dbQueue
}
// Last resort: try to return an empty DB instead of crashing the keyword.
print("Failed to open database \(dbName) even in read-only mode. Returning empty DB.")
return try! DatabaseQueue()
}
}

Expand Down Expand Up @@ -273,6 +292,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 == 9 { return }
}
}
}
}
} catch {}

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

return Array(outputValues.prefix(9))
}

/// Query the noun form of word in `nonuns`.
func queryNounForm(of word: String) -> [String] {
let language = getControllerLanguageAbbr()
Expand Down
1 change: 1 addition & 0 deletions Keyboards/KeyboardsBase/InterfaceVariables.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ enum CommandState {
case invalid
case displayInformation
case dynamicConjugation
case colonToEmoji
}

/// States of the keyboard corresponding to which auto actions should be presented.
Expand Down
100 changes: 98 additions & 2 deletions Keyboards/KeyboardsBase/Keyboard.xib
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,20 @@
<outlet property="leftAutoPartition" destination="0Dt-BE-21v" id="V0x-nQ-quh"/>
<outlet property="padEmojiDivider0" destination="vxG-Xy-AGP" id="TMB-d6-3x2"/>
<outlet property="padEmojiDivider1" destination="a5v-CW-jQ3" id="eXc-pw-atG"/>
<outlet property="padEmojiDivider2" destination="d-PaED-2" id="c-PaED-2"/>
<outlet property="padEmojiDivider3" destination="d-PaED-3" id="c-PaED-3"/>
<outlet property="padEmojiDivider4" destination="d-PaED-4" id="c-PaED-4"/>
<outlet property="padEmojiKey0" destination="ahf-6u-wyz" id="V4O-U0-bUO"/>
<outlet property="padEmojiKey1" destination="rf3-vY-iiK" id="NAu-su-j0i"/>
<outlet property="padEmojiKey2" destination="O2V-5i-PgI" id="Io9-tE-K60"/>
<outlet property="padEmojiKey3" destination="d-PaEK-3" id="c-PaEK-3"/>
<outlet property="padEmojiKey4" destination="d-PaEK-4" id="c-PaEK-4"/>
<outlet property="padEmojiKey5" destination="d-PaEK-5" id="c-PaEK-5"/>
<outlet property="phoneEmojiDivider" destination="6DZ-gU-SO5" id="VXK-ZI-d4Q"/>
<outlet property="phoneEmojiKey0" destination="pTv-BK-rKh" id="U16-av-iWx"/>
<outlet property="phoneEmojiKey1" destination="KVC-Ai-vdF" id="iYf-s9-OKK"/>
<outlet property="phoneEmojiKey2" destination="d-PEK-2" id="c-PEK-2"/>
<outlet property="phoneEmojiDivider1" destination="d-PED-1" id="c-PED-1"/>
<outlet property="pluralKey" destination="FgS-T1-2rI" id="NSp-W5-nas"/>
<outlet property="rightAutoPartition" destination="hPX-B4-WMk" id="vhl-ch-h5H"/>
<outlet property="scribeKey" destination="MCB-7F-dNd" id="AzX-VF-3Nj"/>
Expand Down Expand Up @@ -430,6 +438,70 @@
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title=""/>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d-PEK-2" userLabel="PhoneEmoji2">
<rect key="frame" x="237" y="4" width="36.5" height="30.5"/>
<color key="backgroundColor" systemColor="systemPurpleColor"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d-PED-1" userLabel="PhoneEmojiDivider1">
<rect key="frame" x="276.5" y="7" width="1" height="24.5"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="c-PED-1-w"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="20"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d-PaEK-3" userLabel="PadEmoji3">
<rect key="frame" x="237" y="4" width="22" height="30.5"/>
<color key="backgroundColor" systemColor="systemPurpleColor"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d-PaEK-4" userLabel="PadEmoji4">
<rect key="frame" x="266" y="4" width="22" height="30.5"/>
<color key="backgroundColor" systemColor="systemPurpleColor"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d-PaEK-5" userLabel="PadEmoji5">
<rect key="frame" x="295" y="4" width="22" height="30.5"/>
<color key="backgroundColor" systemColor="systemPurpleColor"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d-PaED-2" userLabel="PadEmojiDivider2">
<rect key="frame" x="276.5" y="7" width="1" height="24.5"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="c-PaED-2-w"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="20"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d-PaED-3" userLabel="PadEmojiDivider3">
<rect key="frame" x="276.5" y="7" width="1" height="24.5"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="c-PaED-3-w"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="20"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d-PaED-4" userLabel="PadEmojiDivider4">
<rect key="frame" x="276.5" y="7" width="1" height="24.5"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="c-PaED-4-w"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="20"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" systemColor="systemGray4Color"/>
Expand All @@ -453,7 +525,25 @@
<constraint firstItem="pjP-qT-t4Q" firstAttribute="leading" secondItem="Yr3-T8-7HB" secondAttribute="trailing" constant="3" id="6RD-80-zzr"/>
<constraint firstItem="rP7-7a-des" firstAttribute="centerY" secondItem="9j4-80-zkW" secondAttribute="centerY" id="6f9-VW-kcg"/>
<constraint firstItem="rP7-7a-des" firstAttribute="leading" secondItem="9j4-80-zkW" secondAttribute="trailing" constant="7" id="6ob-5g-ihe"/>
<constraint firstAttribute="trailing" secondItem="O2V-5i-PgI" secondAttribute="trailing" constant="3" id="7OT-I1-a8u"/>
<constraint firstItem="d-PaED-2" firstAttribute="leading" secondItem="O2V-5i-PgI" secondAttribute="trailing" constant="3" id="c-PaED-2-l"/>
<constraint firstItem="d-PaEK-3" firstAttribute="leading" secondItem="d-PaED-2" secondAttribute="trailing" constant="3" id="c-PaEK-3-l"/>
<constraint firstItem="d-PaED-3" firstAttribute="leading" secondItem="d-PaEK-3" secondAttribute="trailing" constant="3" id="c-PaED-3-l"/>
<constraint firstItem="d-PaEK-4" firstAttribute="leading" secondItem="d-PaED-3" secondAttribute="trailing" constant="3" id="c-PaEK-4-l"/>
<constraint firstItem="d-PaED-4" firstAttribute="leading" secondItem="d-PaEK-4" secondAttribute="trailing" constant="3" id="c-PaED-4-l"/>
<constraint firstItem="d-PaEK-5" firstAttribute="leading" secondItem="d-PaED-4" secondAttribute="trailing" constant="3" id="c-PaEK-5-l"/>
<constraint firstAttribute="trailing" secondItem="d-PaEK-5" secondAttribute="trailing" constant="3" id="c-PaEK-5-t"/>
<constraint firstItem="d-PaEK-3" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="c-PaEK-3-cy"/>
<constraint firstItem="d-PaEK-4" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="c-PaEK-4-cy"/>
<constraint firstItem="d-PaEK-5" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="c-PaEK-5-cy"/>
<constraint firstItem="d-PaEK-3" firstAttribute="height" secondItem="MCB-7F-dNd" secondAttribute="height" id="c-PaEK-3-h"/>
<constraint firstItem="d-PaEK-4" firstAttribute="height" secondItem="MCB-7F-dNd" secondAttribute="height" id="c-PaEK-4-h"/>
<constraint firstItem="d-PaEK-5" firstAttribute="height" secondItem="MCB-7F-dNd" secondAttribute="height" id="c-PaEK-5-h"/>
<constraint firstItem="d-PaEK-3" firstAttribute="width" secondItem="O2V-5i-PgI" secondAttribute="width" id="c-PaEK-3-w"/>
<constraint firstItem="d-PaEK-4" firstAttribute="width" secondItem="O2V-5i-PgI" secondAttribute="width" id="c-PaEK-4-w"/>
<constraint firstItem="d-PaEK-5" firstAttribute="width" secondItem="O2V-5i-PgI" secondAttribute="width" id="c-PaEK-5-w"/>
<constraint firstItem="d-PaED-2" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="c-PaED-2-cy"/>
<constraint firstItem="d-PaED-3" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="c-PaED-3-cy"/>
<constraint firstItem="d-PaED-4" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="c-PaED-4-cy"/>
<constraint firstItem="jkU-RQ-53C" firstAttribute="width" secondItem="gnL-wZ-ogY" secondAttribute="width" id="7U1-qf-lk2"/>
<constraint firstItem="gnL-wZ-ogY" firstAttribute="leading" secondItem="18n-dD-Eou" secondAttribute="trailing" constant="3" id="7Wt-Hk-S4Y"/>
<constraint firstItem="mDi-9w-xbx" firstAttribute="leading" secondItem="rFZ-XE-qHq" secondAttribute="leading" id="7bh-kF-MjN"/>
Expand Down Expand Up @@ -653,7 +743,13 @@
<constraint firstItem="92Z-QS-QCN" firstAttribute="leading" secondItem="dOA-cy-aGo" secondAttribute="leading" id="v6s-O1-2Hv"/>
<constraint firstItem="MCB-7F-dNd" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="3" id="v8j-uv-siq"/>
<constraint firstItem="kON-8k-vtV" firstAttribute="bottom" secondItem="Yr3-T8-7HB" secondAttribute="bottom" id="vMW-zj-MNc"/>
<constraint firstAttribute="trailing" secondItem="KVC-Ai-vdF" secondAttribute="trailing" constant="3" id="vTG-PN-x7C"/>
<constraint firstItem="d-PED-1" firstAttribute="leading" secondItem="KVC-Ai-vdF" secondAttribute="trailing" constant="3" id="c-CED-1-l"/>
<constraint firstItem="d-PEK-2" firstAttribute="leading" secondItem="d-PED-1" secondAttribute="trailing" constant="3" id="c-CEK-2-l"/>
<constraint firstAttribute="trailing" secondItem="d-PEK-2" secondAttribute="trailing" constant="3" id="c-CEK-2-t"/>
<constraint firstItem="d-PEK-2" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="c-CEK-2-cy"/>
<constraint firstItem="d-PEK-2" firstAttribute="height" secondItem="MCB-7F-dNd" secondAttribute="height" id="c-CEK-2-h"/>
<constraint firstItem="d-PEK-2" firstAttribute="width" secondItem="pTv-BK-rKh" secondAttribute="width" id="c-CEK-2-w"/>
<constraint firstItem="d-PED-1" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="c-CED-1-cy"/>
<constraint firstItem="MCB-7F-dNd" firstAttribute="centerY" secondItem="RVF-jy-JTf" secondAttribute="centerY" id="vss-sR-d39"/>
<constraint firstItem="pTv-BK-rKh" firstAttribute="centerY" secondItem="MCB-7F-dNd" secondAttribute="centerY" id="vzM-1R-szV"/>
<constraint firstItem="i3q-kx-6Dw" firstAttribute="trailing" secondItem="IR7-Kd-kHd" secondAttribute="trailing" id="wB9-7V-mOo"/>
Expand Down
Loading