Skip to content

Commit 985446b

Browse files
AsafMahLeanBitLabiBasimgithub-actions[bot]rohanlodhi
authored
Merge upstream LeanType v3.8.8 before 3.10.0 release (#111)
* fix(settings): fix and optimize gboard import Parse Gboard format header dynamically to fix missing words and swapped values. Optimize using bulkInsert to reduce database insertion IPC overhead. * chore: bump version to 3.8.4 Set versionCode to 3840 and versionName to 3.8.4 in build.gradle.kts. Create Fastlane changelog metadata at 3840.txt. * feat(settings): improve text expander ui/ux Add responsive search filtering for text expansion shortcuts. Add quick placeholder selection row with rich cursor-based insert support to Add/Edit Dialog. * feat(settings): polish text expander guide and list Redesign Quick Feature Guide card with styled step badges. Redesign custom shortcuts list items with premium keyword badges and chevrons. Redesign empty state with card illustration layout. * feat(settings): clean up text expander guide Remove redundant template placeholder explanation while retaining the list of supported placeholder tags. * feat(settings): add more expander placeholders Add support and UI representations for %month%, %month_short%, %year%, and %week% placeholders. * feat(settings): add system template placeholders Add and integrate support for %battery%, %device%, and %android% placeholders. * feat(settings): add language placeholder Add and integrate support for %language% placeholder, which expands to the current active keyboard display language. * fix(perf): prevent OOM on background image decode - Add BitmapUtils.decodeSampledBitmap() with two-pass decode and inSampleSize - Use RGB_565 config for non-PNG images to halve memory usage - Use BitmapFactory.decodeStream (with InputStream) instead of decodeFile - Cap background bitmap at 2048px max dimension - Recycle temp bitmap after validation in setBackgroundImage Fixes: Settings.java:527, BackgroundImagePreference.kt:122 * fix(stability): replace force-unwrap !! in hot paths - Colors.kt: use 'let' smart cast and 'error' instead of NPE on missing keyBackground - FloatingKeyboardManager.kt: safe-call on overlayRoot, early return on null - SuggestionStripView.kt: early return true on missing drawable Prevents IME process crashes from null drawables/bitmaps in keyboard rendering paths. * fix(stability): unregister SharedPreferences listener in spell-checker AndroidSpellCheckerService.onDestroy() now unregisters the OnSharedPreferenceChangeListener. Without this, the SharedPreferences implementation kept a strong reference to the service, leaking it through every spell-check session the system bound/unbound. * fix(stability): don't call Looper.prepare() on background thread BackupRestorePreference.kt was calling Looper.prepare() from the ScheduledThreadPool executor, leaking a Looper per restore and posting UI work onto an unreliable thread. Use Handler(Looper.getMainLooper()) to dispatch the FeedbackManager.message call to the UI thread. * fix(stability): make score-limit cache update atomic in Suggest The previous implementation had a non-atomic read-then-write of mLastScoreLimitUpdateTime and mCachedScoreLimitForAutocorrect across threads (suggestion lookup can happen on background threads via SuggestionSpan / TextClassifier). Two threads could both miss the interval check and recompute, with the second write overwriting the first. Wrap the cache update in synchronized(this) to make the check and update atomic. * fix(stability): use named lock for dictionary blacklist Three blacklist operations in DictionaryGroup used `<outer>.apply { scope.launch { synchronized(this) { ... } } }`, which re-bound `this` to the HashSet / CoroutineScope inside the synchronized block. Two threads could enter the critical section concurrently because they were locking on different objects. Add an explicit `blacklistLock: Any` and synchronize on it. * perf(perf): add key= to Lazy* list items for stable identity - SearchScreen: key groups by titleRes, items by toString() - ListPickerDialog, MultiListPickerDialog: key items by toString() - LayoutPickerDialog: key by layout name - ToolbarKeysCustomizer: key by enum name - ColorThemePickerDialog: key by color name Without keys, LazyColumn uses positional keys, causing every visible item to be recomposed (and its remember slots discarded) on every search keystroke or list mutation. * perf(perf): remember() expensive computations in Composables - SearchScreen: cache filteredItems(searchText.text) so it doesn't re-run the search filter on every parent recomposition (only when the search text actually changes) - MainSettingsScreen: cache SubtypeSettings.getEnabledSubtypes() and its joinToString() output so the description string is not rebuilt on every recomposition Both lists are otherwise recomputed on every pref change, every parent state change, and every scroll-induced recomposition. * perf(perf): avoid Paint allocation per recomposition in ColorPickerDialog Wrap the Paint in remember { } and assign to the controller inside a LaunchedEffect so the Paint is created once and not allocated on every recomposition. Also avoids re-assigning the controller's wheelPaint on every recomposition. * perf(perf): stream logcat to file instead of buffering in memory AboutScreen's 'Save log' was reading the entire logcat buffer into a single String via readText(), then writing it out. For a long-running device this can be several MB and compete with the IME process for memory, risking OOM on low-RAM devices. Use useLines { } to iterate line by line and write each one directly to the output stream. The internal log is now also streamed with a for loop and explicit toString() instead of a joinToString() that builds the entire list as a single String. * perf(perf): make ReorderSwitchPreference data class stable The private KeyAndState class had var fields that were mutated in the Switch.onCheckedChange callback, defeating Compose stability and forcing LazyColumn items to be rebuilt on every recomposition. - Make KeyAndState an immutable data class annotated with @immutable - Hold the checked state in rememberSaveable(item.name) so the value survives recomposition but is per-item - Remove the in-place mutation of item.state in the Switch callback - rememberSaveable the items list so it's not re-parsed on every recomposition when the dialog is open * fix(perf): remove top-level MutableStateFlow in AIIntegrationScreen The previous top-level 'providerState' MutableStateFlow lived for the process lifetime and was mutated during composition. The state can be derived from the service on every composition (the service reads from SharedPreferences, which is cheap). - Replace top-level MutableStateFlow with a simple val read - Remove the no-op updateProviderState() function - Remove its call site in AdvancedScreen.kt The AIIntegrationScreen will pick up provider changes on the next composition (e.g. when the user navigates to it after changing the provider on the AdvancedScreen). * fix(perf): scope errorJob to the LayoutEditDialog composable The top-level 'private var errorJob: Job?' was shared between any two simultaneous instances of LayoutEditDialog, so opening a second dialog would cancel the first dialog's pending error feedback job. On configuration change the coroutine scope could be cancelled while the top-level job reference was leaked. Move the job into a per-composable remember { mutableStateOf<Job?>(null) } and cancel/assign through errorJob.value. * fix(perf): replace GlobalScope in toolbar preference listener setToolbarButtonsActivatedStateOnPrefChange used GlobalScope.launch to defer a UI update by 10 ms, waiting for SettingsValues to reload after a SharedPreferences change. GlobalScope is uncancellable and its default exception handler converts failures into silent crashes. Replace it with a process-wide scope that uses SupervisorJob (so one failure cannot tear down sibling preference updates) and a logging CoroutineExceptionHandler. The function still hops to Dispatchers.Main before touching the view tree. * fix(perf): make SettingsNavHost navigateTo scope supervised The CoroutineScope backing the navigateTo() helper used a plain Job, so a single child failure would cancel the scope permanently. Add SupervisorJob so unrelated navigation hops keep working. * fix(stability): replace !! in colorFilter() helper createBlendModeColorFilterCompat returns a nullable ColorFilter, but the helper is only ever called with the supported BlendModeCompat modes (MODULATE, SRC_IN). Replace the !! with a Kotlin error() that throws IllegalStateException with a useful message if a new unsupported mode is ever introduced. * perf(perf): cache main-thread Handler in ClipboardHistoryManager ClipboardHistoryManager is a singleton scoped to the IME service, but it was creating a fresh Handler(Looper.getMainLooper()) on every postDelayed() and on every ContentObserver registration. The main Looper is process-wide and lives for the lifetime of the app, so a single cached Handler is enough. Replace the two ad-hoc Handler allocations in registerMediaStoreObserver and in the post-paste clip restoration path with a single 'mainHandler' field on the manager. * fix(stability): use SupervisorJob in RichInputMethodManager scope The CoroutineScope backing updateShortcutIme, onSubtypeChanged and related fire-and-forget coroutines was using a plain Job. A single exception in any of those coroutines would cancel the scope and stop all subsequent subtype lookups for the lifetime of the IME process. Add SupervisorJob() so a single failure cannot tear down the rest of the lookups. * fix(stability): use ContextCompat.registerReceiver with NOT_EXPORTED flag LatinIME.onCreate was using the deprecated registerReceiver(receiver, filter) overload for the ringer mode, package add/remove and user unlocked broadcasts. On Android 13+ this throws SecurityException unless the receiver is registered with an explicit exported flag. Switch the three call sites to ContextCompat.registerReceiver with RECEIVER_NOT_EXPORTED, matching the existing style used for DICTIONARY_DUMP_INTENT_ACTION. The exported flag stays set for the NEW_DICTIONARY_INTENT_ACTION receiver, as documented in the existing comment, because the sender app may not be this one. * fix(settings): update ai provider fields dynamically Align provider preference source and observe changes dynamically to update UI fields immediately. Wrap preferences in key() to prevent Compose state reuse. * feat: add standardOptimised flavor and disable r8 Add standardOptimised flavor to allow non-reproducible optimizations like R8 fullMode and baseline profiles. Turn off R8 fullMode globally to restore reproducibility for standard flavor on F-Droid. Clean APK metadata and restore global V2/V3 signing. * perf: add baseline profile for standardOptimised Add manual wildcard-based precompilation rules in baseline-prof.txt for standardOptimised to optimize startup, typing reaction, and suggestions. Fix dynamic property injection in settings.gradle. * feat: remove standardOptimised package suffix Remove applicationIdSuffix from standardOptimised product flavor to share standard package name (com.leanbitlab.leantype). * fix: dismiss emoji dialog and update wizard status Close ConfirmationDialog on successful emoji dictionary download/load. Invoke onSuccess callback in WelcomeWizard to trigger recomposition and show the checkmark immediately. * Update ar.txt * Update build-debug-apk.yml * arabic-popup-and-harakat-tweak * arabic-popup-and-harakat-tweak V2 * ci: add badge update workflow * chore: update README badges [skip ci] * fix: strip leading v from version tag * chore: update README badges [skip ci] * fix(badges): adjust width and add viewBox attributes to prevent clipping * chore: update README badges [skip ci] * chore(badges): rename download badge label to version * chore: update README badges [skip ci] * fix: prevent duplicate screenshots in clipboard * feat: add toggle for screenshot compression * feat: improve text expander, gestures, and emoji scale fit * chore: update README badges [skip ci] * fix: persist toolbar customizer key toggles * build: bump version to v3.8.5 * feat: toggle dictionaries individually * chore: add changelog for v3.8.5 * fix: split emoji search keyboard layout * chore: update changelog for emoji search fix * added auto detect feature * changed registration flag * chore: update README badges [skip ci] * Update ar.txt * refactor: replace onnxruntime with llamacpp Switches offline proofreader to llamacpp-kotlin GGUF and updates model settings UI to resolve 16 KB page alignment compatibility warnings. * suggestion delete blacklist always. reload blacklist interface add. * blocked words screen add. dictionary screen integration done. settings strings update. * blacklist check case-insensitive. lowercase canonicalization added. user dictionary suggestion leak resolved. * blacklist regex support added. compiled patterns cached. compile-time receiver errors resolved. * SearchScreen remember key fix. filteredItems lambda dependency added. list auto-refresh working. * fix(layout): align Arabic diacritics spacing * chore: update README badges [skip ci] * chore: update README badges [skip ci] * chore: update README badges [skip ci] * chore: update README badges [skip ci] * chore: update README badges [skip ci] * Allow for reasoning models; handle structured content arrays in API responses Parse JSONArray content format with type and text fields. Extract reasoning_content when main content is blank. Fall back to firstChoice text field if content extraction fails. * feat: add regex expander & fix dictionary crash * feat(touchpad): double tap to select word & fix emoji popup preview * feat(offline): add settings for custom sampling & prompt * chore: update gitignore - add .env, .pi/ and remove duplicate * docs: add F-Droid reproducibility delay notice * chore: bump version to v3.8.6 and add changelog * fix(touchpad): always select word on double tap and update docs * feat(touchpad): implement multi-finger gestures and update docs * feat(touchpad): reorganize gestures for intuitive rich text editing layout * feat(touchpad): migrate gestures to 1 and 2 fingers * docs: update features for llama.cpp migration * docs: note model-dependent accuracy in features * fix(touchpad): exit touchpad mode when opening clipboard or emoji * perf(offline): optimize proofreading latency and load times * fix(offline): improve GGUF prompt formatting and output cleaning * fix(offline): truncate model output at template markers and add native stop sequences * fix(offline): implement dynamic target-language-specific few-shot examples for GGUF translation * feat(expander): immediate expand & fix revert * feat: hold toolbar arrow keys to auto-repeat * chore: update README badges [skip ci] * chore: update README badges [skip ci] * chore: update README badges [skip ci] * feat: add toggle for insecure AI connections Allows HTTP local endpoints and self-signed HTTPS connections only when explicitly enabled by the user. * feat: add selective backup and restore * fix: allow same word with different shortcuts * feat: strip spaces before punctuation marks * chore: update README badges [skip ci] * chore: update README badges [skip ci] * chore: update README badges [skip ci] * chore: update README badges [skip ci] * Change default popup key on letter ا in Persian language * chore: update README badges [skip ci] * feat: add handwriting input support * docs: update 3.8.6 changelog * fix: use wildcard mime type for file picker to avoid waydroid crash * fix: clear code cache directory on plugin import/remove * chore: add MD5 hash and size logging for loaded plugin * feat(handwriting): fix crash and dynamic model downloading Move model readiness checks to background thread to prevent main thread blocking exceptions. Add ML Kit client dependencies to standard build flavor for native library alignment. Auto-upgrade toolbar preferences to discover new keys without factory resets. * feat: fix handwriting layout, theming and logic * style: change handwriting toolbar icon color to white * feat: show shortcut overlay on handwriting canvas when plugin missing * fix(ai): resolve offline custom key token loss Prevent token loss and hallucination in local models due to formatting and JNI bugs. * feat(handwriting): add plugin downloader and refine blacklist * build: limit abi filters to arm64-v8a * docs: document handwriting and gguf features * chore: update README badges [skip ci] * chore: update README badges [skip ci] * fix(handwriting): avoid cancelling active keys when hidden * build: update config, proguard rules, and blacklist parsing * feat: tune double-tap shift timing and keep llamacpp proguard * build: remove standardOptimised flavor * fix: prevent handwriting suggestions from hiding Also hide the redundant top toolbar on the handwriting panel during normal use, keeping it only for active download progress. * chore: update README badges [skip ci] * fix(toolbar): restore close/search on clipboard * fix(settings): add label for clipboard_search key * feat(settings): allow deleting handwriting model * feat(dict): add dynamic dictionary downloader * feat(dict): allow uninstalling downloaded dicts * feat(dict): improve dynamic downloading flow * fix: keep number row digits when keyboard is shifted The number row layout used a shift_state_selector whose manualOrLocked branch rendered the shifted symbol (!@#...) in place of the digit, so engaging shift or shift-lock replaced 1234567890 with !@#$%^&*(). The keys are now plain digit keys in every shift state, with the shifted symbol kept as the first popup ahead of the existing fraction popups. Fixes LeanBitLab#180 * feat(dict): exclude non-en-US dictionaries from standard flavor assets * feat(dict): show download button on toolbar if layout dictionary is not loaded * fix: do not show disabled additional subtypes in dict settings list * chore: add v3.8.7 and v3.8.8 changelogs * fix: prevent WindowManager$BadTokenException in IME overlay dialog * fix: only update split toolbar emoji recents when view is visible * feat(emoji): close search on dictionary download * feat(emoji): show download button in split toolbar * chore: update changelog for 3.8.8 * chore: bump version to 3.8.8 * feat(handwriting): add download button to plugin required overlay * chore: add handwriting plugin downloader to 3.8.8 changelog * docs: temporarily hide F-Droid badge from README * docs: remove F-Droid column from table to fix spacing * docs: move download section above screenshots in README * docs: remove fork AI feature description line from README * docs: add Dynamic Downloader to README features * docs: add Selective Backup, Blacklist, and OTP features to README * docs: sort features by significance in README * chore: update README badges [skip ci] --------- Co-authored-by: LeanBitLab <leanbitlab@users.noreply.github.com> Co-authored-by: iBasim <57762287+iBasim@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Rohan Lodhi <42321434+RohanLodhi@users.noreply.github.com> Co-authored-by: LeanBitLab <arjunathira2222@gmail.com> Co-authored-by: David C <code@davidc.xyz> Co-authored-by: nugraha-abd <62243267+nugraha-abd@users.noreply.github.com> Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
1 parent 062fab2 commit 985446b

40 files changed

Lines changed: 1083 additions & 506 deletions

File tree

.github/workflows/update-badges.yml

Lines changed: 0 additions & 78 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
2222
- **Auto-read OTP from SMS** — a one-time code from an incoming SMS is offered in the suggestion
2323
strip while the keyboard is open; tap to insert. Uses a runtime, opt-in SMS permission.
2424
- **Regex shortcuts in Text Expander** — expansion triggers can be matched by regular expression.
25+
- **Dynamic dictionary/plugin downloader** — Standard builds can fetch layout dictionaries, emoji dictionaries, and handwriting plugins on demand.
26+
- **Selective backup and restore** — backup/restore settings, dictionaries, and AI prompt configuration more granularly.
2527

2628
### Changed
2729
- **Offline AI backend switched from ONNX Runtime to llama.cpp (GGUF).** The Offline build now
@@ -31,11 +33,19 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
3133
navigation, space, copy/paste, cut/select-all, undo/redo, hold-to-backspace). Single-finger
3234
double-tap now **selects the word** (previously deleted the selection).
3335
- Release builds now target the **arm64-v8a** ABI only.
36+
- Standard builds now exclude non-en-US dictionary assets and download optional dictionaries dynamically.
37+
38+
### Fixed
39+
- **Sticky Shift from upstream handwriting cleanup** — upstream v3.8.6 stopped the hidden handwriting
40+
bottom row on every keyboard-frame switch, which globally cancelled the active Shift pointer before
41+
release. We keep the upstream handwriting feature but only stop handwriting when it is actually
42+
shown. (Upstream bug LeanBitLab/LeanType#186; upstream PR #194.)
3443

3544
### Upstream
36-
- Merged **LeanBitLab/LeanType v3.8.6** (from v3.8.3) — the source of the handwriting,
37-
llama.cpp/GGUF, touchpad-gesture, and SMS-OTP changes above. Fork identity (LeanTypeDual, distinct
38-
`applicationId`, two-thumb typing, the Gemini standard-AI layer, and the privacy tiers) is
45+
- Merged **LeanBitLab/LeanType v3.8.8** (from v3.8.3, including v3.8.7 and two post-tag docs/badge
46+
commits) — the source of the handwriting, llama.cpp/GGUF, dynamic downloader, touchpad-gesture,
47+
SMS-OTP, selective-backup, and dictionary-downloader changes above. Fork identity (LeanTypeDual,
48+
distinct `applicationId`, two-thumb typing, the Gemini standard-AI layer, and the privacy tiers) is
3949
preserved.
4050

4151
## [3.9.1] - 2026-06-11

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@ Type with **both thumbs gliding at the same time**: LeanTypeDual aggregates mult
3030
- **🧠 Smarter learned words** - *graduated trust* keeps a just-learned word below real-dictionary suggestions until you've used it a few times (no premature autocorrect to half-typed words); flag unknown words to **Add** or **Block** them via a Blocklist screen.
3131
- **↩️ Undo word** - a toolbar key that reverts the last committed word back to its suggestion alternatives.
3232
- **🗂️ Per-dictionary control** - enable or disable individual built-in and custom dictionaries.
33+
- **📥 Dynamic Downloader** - Standard builds can download layout dictionaries, emoji dictionaries, and handwriting plugins on demand, keeping the initial app smaller.
3334
- **🪟 Floating Keyboard** - Detach the keyboard into a draggable, resizable window (true OS-level overlay), with an optional persistent mode.
3435
- **⌨️ Dual Toolbar / Split Suggestions** - Split the suggestion strip and toolbar for easier reach.
3536
- **🖱️ Touchpad Mode** - Swipe the spacebar up for a cursor touchpad with sensitivity controls and edge-scroll acceleration, including a full-screen laptop-style mode.
3637
- **🎨 Modern UI** - "Squircle" key backgrounds, refined icons, and polished aesthetics.
3738
- **🔄 Google Dictionary Import** - Import your personal dictionary words.
3839
- **🔍 Clipboard Search & Undo** - Search clipboard history from the toolbar, undo accidental deletions, and fold pinned items by default.
3940
- **📸 Screenshot Suggestion & Clipboard** - Recently-taken screenshots are offered in the suggestion strip and saved to clipboard history.
41+
- **✉️ Auto-Read OTP** - Incoming one-time codes can appear in the suggestion strip for quick insertion.
42+
- **💾 Selective Backup & Restore** - Backup and restore settings, dictionaries, and AI prompt configuration selectively.
4043
- **🔎 Emoji Search** - Search emojis by name. *Requires loading an Emoji Dictionary.*
4144
- **⚙️ Enhanced Customization** - Force auto-capitalization, fine-grained haptics, distinct incognito icon, reorganized settings, and more.
4245
- **🔒 Privacy Choices** - Choose **Standard** (opt-in AI, handwriting), **Offline** (network hard-disabled, offline GGUF model), or **Offline Lite** (no AI, ~20 MB).

app/build.gradle.kts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ android {
3636
productFlavors {
3737
create("standard") {
3838
dimension = "privacy"
39-
}
40-
create("standardOptimised") {
41-
dimension = "privacy"
39+
minSdk = 23
4240
}
4341
create("offline") {
4442
dimension = "privacy"
@@ -106,7 +104,6 @@ android {
106104
"standard" -> "1"
107105
"offline" -> "2"
108106
"offlinelite" -> "3"
109-
"standardOptimised" -> "4"
110107
else -> ""
111108
}
112109
if (number.isNotEmpty()) {
@@ -119,13 +116,28 @@ android {
119116
}
120117
// got a little too big for GitHub after some dependency upgrades, so we remove the largest dictionary
121118
androidComponents.onVariants { variant: ApplicationVariant ->
119+
val patterns = mutableListOf<String>()
122120
if (variant.buildType == "debug") {
123-
variant.androidResources.ignoreAssetsPatterns = listOf("main_ro.dict")
121+
patterns.add("main_ro.dict")
124122
variant.proguardFiles = emptyList()
125123
//noinspection ProguardAndroidTxtUsage we intentionally use the "normal" file here
126124
variant.proguardFiles.add(project.layout.buildDirectory.file(getDefaultProguardFile("proguard-android.txt").absolutePath))
127125
variant.proguardFiles.add(project.layout.buildDirectory.file(project.buildFile.parent + "/proguard-rules.pro"))
128126
}
127+
if (variant.flavorName == "standard") {
128+
// ponytail: dynamically find all dict files to ignore in standard flavor except main_en-US.dict
129+
val dictsDir = project.file("src/main/assets/dicts")
130+
if (dictsDir.exists() && dictsDir.isDirectory) {
131+
dictsDir.listFiles()?.forEach { file ->
132+
if (file.name.endsWith(".dict") && file.name != "main_en-US.dict") {
133+
patterns.add(file.name)
134+
}
135+
}
136+
}
137+
}
138+
if (patterns.isNotEmpty()) {
139+
variant.androidResources.ignoreAssetsPatterns = patterns
140+
}
129141
}
130142
}
131143

@@ -194,14 +206,6 @@ android {
194206
// these orphaned strings are harmlessly stripped by R8 during minification.
195207
disable += "ExtraTranslation"
196208
}
197-
198-
sourceSets {
199-
getByName("standardOptimised") {
200-
java.srcDirs("src/standard/java")
201-
res.srcDirs("src/standard/res")
202-
manifest.srcFile("src/standard/AndroidManifest.xml")
203-
}
204-
}
205209
}
206210

207211
dependencies {
@@ -230,8 +234,6 @@ dependencies {
230234
// gemini ai proofreading
231235
"standardImplementation"("com.google.ai.client.generativeai:generativeai:0.9.0")
232236
"standardImplementation"("androidx.security:security-crypto:1.1.0-alpha06") // for encrypted API key storage
233-
"standardOptimisedImplementation"("com.google.ai.client.generativeai:generativeai:0.9.0")
234-
"standardOptimisedImplementation"("androidx.security:security-crypto:1.1.0-alpha06")
235237

236238
// local llm proofreading (offline)
237239
"offlineImplementation"("io.github.ljcamargo:llamacpp-kotlin:0.4.0")
@@ -248,7 +250,6 @@ dependencies {
248250
// ML Kit's internal asset manager and native library loader use the host app context,
249251
// so the host app must compile and include the client library resources/libraries.
250252
"standardImplementation"("com.google.mlkit:digital-ink-recognition:19.0.0")
251-
"standardOptimisedImplementation"("com.google.mlkit:digital-ink-recognition:19.0.0")
252253

253254
// test
254255
testImplementation(kotlin("test"))
@@ -267,11 +268,9 @@ dependencies {
267268
"runTestsImplementation"("androidx.compose.ui:ui-test-manifest")
268269
}
269270

270-
// Disable baseline/ART profile tasks to guarantee deterministic reproducible builds (except for standardOptimised)
271+
// Disable baseline/ART profile tasks to guarantee deterministic reproducible builds
271272
tasks.configureEach {
272273
if (name.contains("ArtProfile", ignoreCase = true)) {
273-
if (!name.contains("StandardOptimised", ignoreCase = true)) {
274-
enabled = false
275-
}
274+
enabled = false
276275
}
277276
}

app/proguard-rules.pro

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
# Keep java-llama.cpp classes
2929
-keep class de.kherud.llama.** { *; }
30+
-keep class org.nehuatl.llamacpp.** { *; }
31+
3032

3133

3234
# Fix correct service name
@@ -36,3 +38,20 @@
3638
-dontwarn com.google.api.client.**
3739
-dontwarn java.lang.management.**
3840
-dontwarn org.joda.time.**
41+
42+
# Keep handwriting plugin interface and listener to prevent parameter removal/signature optimization
43+
-keep interface helium314.keyboard.latin.handwriting.HandwritingRecognizer {
44+
<methods>;
45+
}
46+
-keep interface helium314.keyboard.latin.handwriting.ModelDownloadListener {
47+
<methods>;
48+
}
49+
50+
# Keep ML Kit, GMS Tasks, and Firebase components for handwriting plugin dynamic linkage
51+
-keep class com.google.mlkit.** { *; }
52+
-keep class com.google.android.gms.tasks.** { *; }
53+
-keep class com.google.firebase.components.** { *; }
54+
55+
# Keep Kotlin standard library for dynamically loaded plugins
56+
# ponytail: keep kotlin stdlib classes to prevent NoSuchMethodError in plugin loading
57+
-keep class kotlin.** { *; }
Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,14 @@
1-
[
2-
[
3-
{ "$": "shift_state_selector",
4-
"manualOrLocked": { "label": "!" },
5-
"default": { "label": "1", "popup": { "relevant": [{ "label": "¹" }, { "label": "½" }, { "label": "" }, { "label": "¼" }, { "label": "" }] } }
6-
},
7-
{ "$": "shift_state_selector",
8-
"manualOrLocked": { "label": "@" },
9-
"default": { "label": "2", "popup": { "relevant": [{ "label": "²" }, { "label": "" }] } }
10-
},
11-
{ "$": "shift_state_selector",
12-
"manualOrLocked": { "label": "#" },
13-
"default": { "label": "3", "popup": { "relevant": [{ "label": "³" }, { "label": "¾" }, { "label": "" }] } }
14-
},
15-
{ "$": "shift_state_selector",
16-
"manualOrLocked": { "label": "$" },
17-
"default": { "label": "4", "popup": { "relevant": [{ "label": "" }] } }
18-
},
19-
{ "$": "shift_state_selector",
20-
"manualOrLocked": { "label": "%" },
21-
"default": { "label": "5", "popup": { "relevant": [{ "label": "" }, { "label": "" }] } }
22-
},
23-
{ "$": "shift_state_selector",
24-
"manualOrLocked": { "label": "^" },
25-
"default": { "label": "6", "popup": { "relevant": [{ "label": "" }] } }
26-
},
27-
{ "$": "shift_state_selector",
28-
"manualOrLocked": { "label": "&" },
29-
"default": { "label": "7", "popup": { "relevant": [{ "label": "" }, { "label": "" }] } }
30-
},
31-
{ "$": "shift_state_selector",
32-
"manualOrLocked": { "label": "*" },
33-
"default": { "label": "8", "popup": { "relevant": [{ "label": "" }] } }
34-
},
35-
{ "$": "shift_state_selector",
36-
"manualOrLocked": { "label": "(" },
37-
"default": { "label": "9", "popup": { "relevant": [{ "label": "" }] } }
38-
},
39-
{ "$": "shift_state_selector",
40-
"manualOrLocked": { "label": ")" },
41-
"default": { "label": "0", "popup": { "relevant": [{ "label": "" }, { "label": "" }, { "label": "" }] } }
42-
}
43-
]
44-
]
1+
[
2+
[
3+
{ "label": "1", "popup": { "relevant": [{ "label": "!" }, { "label": "¹" }, { "label": "½" }, { "label": "" }, { "label": "¼" }, { "label": "" }] } },
4+
{ "label": "2", "popup": { "relevant": [{ "label": "@" }, { "label": "²" }, { "label": "" }] } },
5+
{ "label": "3", "popup": { "relevant": [{ "label": "#" }, { "label": "³" }, { "label": "¾" }, { "label": "" }] } },
6+
{ "label": "4", "popup": { "relevant": [{ "label": "$" }, { "label": "" }] } },
7+
{ "label": "5", "popup": { "relevant": [{ "label": "%" }, { "label": "" }, { "label": "" }] } },
8+
{ "label": "6", "popup": { "relevant": [{ "label": "^" }, { "label": "" }] } },
9+
{ "label": "7", "popup": { "relevant": [{ "label": "&" }, { "label": "" }, { "label": "" }] } },
10+
{ "label": "8", "popup": { "relevant": [{ "label": "*" }, { "label": "" }] } },
11+
{ "label": "9", "popup": { "relevant": [{ "label": "(" }, { "label": "" }] } },
12+
{ "label": "0", "popup": { "relevant": [{ "label": ")" }, { "label": "" }, { "label": "" }, { "label": "" }] } }
13+
]
14+
]

app/src/main/java/helium314/keyboard/keyboard/TouchpadView.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,8 @@ private void setupTouchSurface() {
376376
mIsTwoFingerTap = false;
377377
removeCallbacks(mTwoFingerLongPressRunnable);
378378
mIsTwoFingerLongPress = false;
379+
mTwoFingerTapCount = 0;
380+
removeCallbacks(mTwoFingerTapRunnable);
379381
if (mSelectionMode) {
380382
mSelectionMode = false;
381383
applySurfaceColor();
@@ -385,6 +387,7 @@ private void setupTouchSurface() {
385387
case MotionEvent.ACTION_CANCEL:
386388
android.util.Log.i("TouchpadView", "ACTION_CANCEL");
387389
mIsDragging = false;
390+
stopEdgeScrolling();
388391
mIsTwoFingerScroll = false;
389392
mIsTwoFingerTap = false;
390393
removeCallbacks(mTwoFingerLongPressRunnable);

0 commit comments

Comments
 (0)